diff --git a/automation/sentry-triage/DESIGN_CHOICES.md b/.devin/automation/sentry-triage/DESIGN_CHOICES.md similarity index 100% rename from automation/sentry-triage/DESIGN_CHOICES.md rename to .devin/automation/sentry-triage/DESIGN_CHOICES.md diff --git a/.devin/automation/sentry-triage/README.md b/.devin/automation/sentry-triage/README.md new file mode 100644 index 000000000000..1b860d743fe2 --- /dev/null +++ b/.devin/automation/sentry-triage/README.md @@ -0,0 +1,7 @@ +# .devin/automation/sentry-triage + +- **Skill:** [`SKILL.md`](SKILL.md) — parent workflow (fetch, group, dedupe, spawn subagents, reconcile) +- **Subagent contract:** [`SUBAGENT_CONTRACT.md`](SUBAGENT_CONTRACT.md) — per-group parallel worker rules (self-contained; subagents do not read `SKILL.md`) +- **Principles:** [`DESIGN_CHOICES.md`](DESIGN_CHOICES.md) — general classification principles (not solutions) +- **State:** `ledger/.json` (one file per issue, plus `ledger/_meta.json` for constants) — per-file layout avoids merge conflicts when parallel subagents update different rows +- **Scripts:** [`scripts/find-existing-pr.sh`](scripts/find-existing-pr.sh) — deterministic existing-PR lookup diff --git a/.devin/automation/sentry-triage/SKILL.md b/.devin/automation/sentry-triage/SKILL.md new file mode 100644 index 000000000000..9608f9852f38 --- /dev/null +++ b/.devin/automation/sentry-triage/SKILL.md @@ -0,0 +1,222 @@ +--- +name: sentry-triage +description: Run Fern CLI Sentry false-positive triage for buildwithfern/cli. Use when investigating Sentry CLI issues, classifying false positives, updating the triage ledger, or creating PRs for Sentry error fixes. +--- + +# Sentry CLI False-Positive Triage + +## Scope + +- Sentry org: `buildwithfern` +- Sentry project: `cli` +- Use the repo-baked Sentry CLI: `pnpm exec sentry-cli`. +- Default query: unresolved only, unless the task explicitly says to re-check resolved issues. + +## Required Files + +Read these before proposing code changes: + +1. `.devin/automation/sentry-triage/DESIGN_CHOICES.md` — general principles, not pre-written solutions. +2. `.devin/automation/sentry-triage/ledger/` — one JSON file per `shortId` (e.g. `ledger/CLI-2W.json`); plus `ledger/_meta.json` for repo-wide constants. Read only the rows you need. +3. `.devin/automation/sentry-triage/SUBAGENT_CONTRACT.md` — passed to every spawned subagent. + +The ledger is **one file per `shortId`**. Parallel subagent branches touch disjoint files, so merge conflicts are eliminated by construction. Do not load or rewrite the whole directory unless a task explicitly requires broad record maintenance. + +## Workflow Overview + +1. **Parent** — fetch Sentry, ledger-filter (cheap lookup), existing-PR dedupe (script), group by call site (hypothesis from Sentry stacks), write Group Plans, spawn. +2. **Spawn** — strict 1:1: one parallel subagent per eligible group. N groups → N parallel subagents, never one subagent told to handle several groups and never sequential. See [Subagent Orchestration](#subagent-orchestration). +3. **Reconcile** — collect subagent results, sanity-check ledger, summarize, optionally open one **parent reconciliation PR**. See [Parent Reconciliation](#parent-reconciliation). + +**Classification authority lives in the subagent**, not the parent. The parent does cheap, structural decisions (ledger filter, dedupe, grouping hypothesis). The subagent has the code in front of it and owns the actual disposition call — including `keep_sentry`, `ignored`, `pending_review`, and "the parent's grouping was wrong." The parent's Group Plan is a working hypothesis, not a binding classification. + +## Fetch + +```bash +pnpm exec sentry-cli issues list \ + --org buildwithfern \ + --project cli \ + --status unresolved + +pnpm exec sentry-cli issues list \ + --org buildwithfern \ + --project cli \ + --id +``` + +If the baked CLI cannot show enough detail for a `shortId`, use the Sentry API through `pnpm exec sentry-cli` or an authenticated project-approved Sentry command. Keep payloads small. + +## Ledger Filter + +1. Collect current `shortId`s from Sentry. +2. Query only those rows: + +```bash +for id in CLI-2W CLI-2V; do + path=".devin/automation/sentry-triage/ledger/$id.json" + if [[ -f "$path" ]]; then + jq --arg shortId "$id" '{shortId: $shortId, disposition, duplicateOf, problemSignature, prOrIssue}' "$path" + else + jq -n --arg shortId "$id" '{shortId: $shortId, status: "unknown"}' + fi +done +``` + +3. Skip exact `shortId` files with terminal disposition: `shipped`, `ignored`, `duplicate`, or `keep_sentry`. +4. Re-process `pending_review`. +5. Similar `problemSignature` text is only a hint. Never treat a similar past issue as proof the current `shortId` is fixed. +6. Listing all rows: `ls .devin/automation/sentry-triage/ledger/CLI-*.json`. `_meta.json` holds repo-wide constants and rarely changes. + +## Existing-PR Dedupe Procedure + +After the ledger filter, run the dedupe script for every remaining `shortId`. Do **not** run ad-hoc `gh` searches for this purpose. + +```bash +gh auth status +.devin/automation/sentry-triage/scripts/find-existing-pr.sh CLI-44 CLI-4T +``` + +Steps the script performs, per `shortId`: + +1. **Ledger lookup** — `prOrIssue` and `disposition` from `ledger/.json`; if `prOrIssue` is a PR URL, fetch PR state (any branch). +2. **Triage-branch search** — one `gh pr list` call per run for open PRs with head branch prefix `fix/cli-sentry-triage/`; per `shortId`, match when the branch name, title, or body contains the id (case-insensitive). +3. **Verification metadata** — for each candidate, fetches `files`, `title`, `body`, `state`, `headRefName`. + +PR discovery does **not** search all open PRs in the repo — only triage-shaped branches (plus ledger URLs). + +Stdout is a JSON array (one object per `shortId` with `ledger` and `candidates`). For each `shortId`, judge against the boundary you identified: + +- `none` — no candidate covers the boundary; eligible for a subagent. +- `covered` — a candidate PR's diff/title/body matches the group's boundary and fix; record the `prOrIssue` update for the parent reconciliation PR (do not spawn). +- `partial` — some `shortId`s covered, others not; split the group and only spawn for uncovered `shortId`s. + +If the script exits non-zero (auth failure, rate limit, network), **halt the run** and ask a human to fix `gh` auth or rate before proceeding. The `--limit 100` triage-branch cap is unlikely to be hit; if exceeded, narrow the filter and re-run. + +Env vars: `FERN_REPO` (default `fern-api/fern`), `LEDGER_DIR` (default `.devin/automation/sentry-triage/ledger`). + +## Grouping Rule + +Group by **call site**, not by title alone. + +- A call-site group is a set of Sentry issues that fail from the same function, boundary, or throw/catch path and can be fixed by the same code change. +- Grouping should be rare. Most Sentry issues are unique and should become their own investigation and PR. +- Multiple `shortId`s may share one PR only when the stack proves the same function or boundary produces the same false-positive classification. +- Do not group issues just because the messages look similar, the same dependency appears, or a prior ledger row has a similar `problemSignature`. +- The parent **finalizes the group list before spawning**. Mid-stream regrouping is not allowed. If a subagent disproves a group, it stops and returns findings; the parent re-plans on the next run. + +**Parent investigation scope.** Parent reads Sentry stacks and may optionally grep the codebase for the top frame. **Do not open source files** — the subagent does the source-code reading. The Group Plan is built from Sentry data and ledger context, nothing deeper. + +Before spawning, write one **Group Plan** block per group. The plan is the parent's **working hypothesis**; the subagent verifies it against code and may return any disposition. + +```text +Group: +ShortIds: +Evidence: + - top frames (>=3, with file:line where available) + - exception type and message + - breadcrumb tail if relevant + - event count and lastSeen +ExistingPR: +Branch: fix/cli-sentry-triage/- +``` + +`Evidence` is the subagent's only window into Sentry — the subagent is forbidden from refetching, so include everything it needs to locate and verify the boundary. Paste the data as text; no need for structured JSON. + +The parent does **not** propose a fix or pre-match `DESIGN_CHOICES.md` principles — the subagent owns both once it reads the code. The parent contributes only what it can reliably produce from Sentry data: the call-site hypothesis, the `ShortIds` set, the raw evidence, the dedupe result, and the branch name. The subagent surfaces any new principle worth recording via `newPrincipleProposal`. + +Any group with no `ExistingPR` coverage and no terminal ledger row is eligible for a subagent. The parent **must not** pre-classify groups as `keep_sentry`, `ignored`, or `pending_review` to avoid spawning; those decisions belong to the subagent. The only parent-side shortcuts are: + +- Skip when the ledger row is already terminal (`shipped`, `duplicate`, `keep_sentry`, `ignored`). +- Skip when the dedupe script confirms an existing PR fully covers the group (record `prOrIssue` for the parent reconciliation PR). + +Anything else gets a subagent. + +## Subagent Orchestration + +**One subagent per eligible group. Strict 1:1, no exceptions.** + +For every eligible group from [Grouping Rule](#grouping-rule), the parent spawns exactly one subagent. The mapping is: + +| 1 Group Plan | 1 subagent | 1 branch | 1 PR | 1 disjoint set of `ShortIds` | + +- N eligible groups → **N parallel subagents in the same spawn batch**. Never one subagent handed multiple Group Plans; never sequential spawning to "see how the first one goes". +- Spawn via the host's parallel-worker primitive: + - **Devin**: managed Devins (one separate session per group). + - **Cursor**: `Task` tool with `run_in_background: true`, called once per group within a single message. + - Other hosts: the equivalent parallel-worker mechanism. +- Subagent rules live in [`SUBAGENT_CONTRACT.md`](SUBAGENT_CONTRACT.md). Pass only that file's path plus **one** Group Plan block. Never paste multiple Group Plans into one spawn prompt. +- Each subagent works on its own fresh branch off `main`. Branches must **not** be stacked across groups. +- Each subagent owns: its branch, its fix, its PR, and ledger files for its `ShortIds` only. + +Anti-patterns (do not do these): + +- Spawning one subagent with a list of Group Plans ("here are 5 groups, work through them"). +- Spawning subagents sequentially across groups, even when the parent feels uncertain about parallelism. +- Letting one subagent open a second PR because it noticed another group while reading code (that's a re-plan, not a scope expansion — see [Stop and return findings](SUBAGENT_CONTRACT.md#stop-and-return-findings-no-pr-if)). +- Bundling several `ShortIds` from different call sites into one "miscellaneous" group to spawn fewer subagents. + +If the parent is uncertain a group is well-formed, leave it unspawned and roll it into the next run — skipping is better than merging groups. + +Parent spawn prompt (uniform for every group, one spawn per group): + +```text +Read .devin/automation/sentry-triage/SUBAGENT_CONTRACT.md and follow it. + +Group Plan: + +``` + +## Parallel-Safety Rules + +- Branch names per group are unique by the date + `shortId` scheme (each `shortId` belongs to exactly one group). +- Ledger writes are partitioned by `shortId` across separate files (`ledger/.json`); parallel subagents never touch the same file. **This is the primary reason for the per-file layout.** +- `DESIGN_CHOICES.md` is parent-only. Subagents return `newPrincipleProposal` instead of editing it. +- `ledger/_meta.json` is parent-only and rarely edited. +- If two groups share a code path mid-investigation, both subagents stop and return findings; the parent re-plans. + +## Parent Reconciliation + +After all subagents finish: + +1. Collect each subagent's `prUrl` (or `skipped: `), `shortIdsTouched`, `principlesApplied`, `newPrincipleProposal`. +2. **Ledger collision check** (per-file layout makes this trivial): no two open triage PRs should include the same `ledger/CLI-XX.json` file. Verify with `gh pr view --json files` against each open triage PR. +3. Print a per-group summary: group label, `ShortIds`, PR URL or skip reason. +4. **Optional parent reconciliation PR** (only when there is something to write): + - Ledger `prOrIssue` updates for groups the parent marked `covered` during dedupe. + - 1–3 new `DESIGN_CHOICES.md` bullets surfaced via `newPrincipleProposal`, after human review. + + The reconciliation PR contains **no code changes**, only ledger pointer updates and/or `DESIGN_CHOICES.md` edits. Branch: `chore/cli-sentry-triage/-reconcile`. + +## PR Rules + +- One PR per call-site solution group, opened by the subagent (see [Subagent Orchestration](#subagent-orchestration)). +- A run may create multiple PRs; do not create one catch-all triage PR. +- Before creating a branch or PR, the [Existing-PR Dedupe Procedure](#existing-pr-dedupe-procedure) must have run; if an open triage PR already addresses the group, do not spawn a duplicate subagent. +- If a re-run finds the same `ShortIds` still unresolved **and** an open triage PR already exists at the right boundary, push additional commits to that branch rather than creating a new PR (host-agent convention). Most cases never reach here because terminal ledger rows are skipped at the ledger-filter step. +- Branch: `fix/cli-sentry-triage/{date}-` (e.g. `fix/cli-sentry-triage/2026-11-03-cli-44-cli-4t-cli-4v`). +- Use ISO date (`YYYY-MM-DD`) and at most three `shortId`s in the branch name, lowercased and kebab-cased. +- PR title must include the solved `shortId`s. If more than three, list the first three and summarize the family. +- PR description lists every solved `shortId` and a short fix note. +- Update only the per-`shortId` files under `.devin/automation/sentry-triage/ledger/` for that group, in the same PR as the code. Never touch other rows' files or `_meta.json`. +- Add `packages/cli/cli/changes/unreleased/` when CLI behavior or reporting changes. + +## Ledger Records + +One file per issue at `.devin/automation/sentry-triage/ledger/.json`. Required fields: + +- `title` +- `problemSignature` +- `disposition` +- `rationale` +- `fixSummary` +- `prOrIssue` +- `lastAnalyzed` +- `duplicateOf` when `disposition` is `duplicate` + +The `shortId` is the filename (no `shortId` field inside the file). `ledger/_meta.json` holds repo-wide constants (`schemaVersion`, `project`) and is rarely edited. + +Disposition values: `shipped`, `duplicate`, `keep_sentry`, `ignored`, `pending_review`. `pending_review` is the only non-terminal value (re-processed next run); the rest are terminal. Full decision rules live in [`SUBAGENT_CONTRACT.md`](SUBAGENT_CONTRACT.md) (`Choosing a disposition`). + +## Sentry Resolution + +Do not mark Sentry issues resolved unless the exact shipped CLI release is known and explicitly provided. Prefer explicit release resolution over "next release". diff --git a/.devin/automation/sentry-triage/SUBAGENT_CONTRACT.md b/.devin/automation/sentry-triage/SUBAGENT_CONTRACT.md new file mode 100644 index 000000000000..f45f16b8be69 --- /dev/null +++ b/.devin/automation/sentry-triage/SUBAGENT_CONTRACT.md @@ -0,0 +1,121 @@ +# Sentry triage subagent contract + +You are a Sentry triage subagent for **exactly one** call-site group. The parent must spawn you with a single Group Plan block; the contract is 1:1. + +If the input contains more than one Group Plan, or asks you to "handle multiple groups", **stop immediately** and return `error: parent violated 1:1 spawn rule — refusing to merge groups`. Do not pick one and proceed; do not split the work yourself. The parent is responsible for re-spawning one subagent per group. + +Read this file fully before acting. + +## Required reads + +- This file (`SUBAGENT_CONTRACT.md`) — your full operating rules; do **not** read `SKILL.md` (parent workflow only). +- `.devin/automation/sentry-triage/DESIGN_CHOICES.md` — general principles for classification while implementing. +- `.devin/automation/sentry-triage/ledger/.json` — one file per issue. Read only the files for your `ShortIds`; do not list or load the rest of the directory. + +## Input + +The parent provides exactly one **Group Plan** block with these fields: + +`Group`, `ShortIds`, `Evidence`, `ExistingPR`, `Branch` + +Treat the `ShortIds` and `Branch` as authoritative. Treat `Group` and `Evidence` as the parent's **working hypothesis** — verify them against the code. The parent does not propose a fix or pre-match principles; both are your job once you read the code and `DESIGN_CHOICES.md`. If the hypothesis is wrong, stop and return findings; do not regroup, refetch Sentry, or expand scope. + +## Classification authority + +You — the subagent — own the disposition decision for every `shortId` in your group. The parent never pre-classifies as `keep_sentry`, `ignored`, or `pending_review`; it only skips groups that are already terminal in the ledger or fully covered by an existing PR. Every other group reaches you, and you decide using [Choosing a disposition](#choosing-a-disposition). + +You may: + +- Determine a fix at the named call site that solves all your `ShortIds`, then ship it (→ `shipped`). The fix must respect `DESIGN_CHOICES.md`. +- Conclude the issue is a real product bug after reading the code (→ `keep_sentry`). +- Conclude no code change is warranted (→ `ignored`, with the matching case in `rationale`). +- Conclude you genuinely cannot classify (→ `pending_review`, with the matching case in `rationale`). +- Stop and return findings when the group's boundary turns out to be wrong (parent re-plans). + +You may **not** change `ShortIds`, expand scope, or open more than one PR. + +## Ledger rows (your `ShortIds` only) + +Each issue is its own file at `.devin/automation/sentry-triage/ledger/.json`. The `shortId` is the filename — do not add a `shortId` field inside the file. Edit (or create) **only the files matching your `ShortIds`**; never touch others, never list the directory. + +Preserve or set these fields per file: + +- `title`, `problemSignature` — keep existing values unless missing (then use context from the Group Plan). +- `disposition` — one of `shipped`, `duplicate`, `keep_sentry`, `ignored`, `pending_review`. See [Choosing a disposition](#choosing-a-disposition) for the decision rules. +- `duplicateOf` — set to the canonical `shortId` only when `disposition` is `duplicate`. +- `rationale` — why this is (or is not) a false positive; for `ignored` and `pending_review`, state which specific case from the decision rules applies. +- `fixSummary` — one line describing the boundary fix (omit or leave blank for non-`shipped` rows). +- `prOrIssue` — PR URL once the PR is open. +- `lastAnalyzed` — today's date (`YYYY-MM-DD`). + +Do not edit `ledger/_meta.json`. Do not mark Sentry issues resolved in Sentry itself. + +## Choosing a disposition + +Pick exactly one disposition per `shortId`. Four are terminal (`shipped`, `duplicate`, `keep_sentry`, `ignored`); `pending_review` is the only non-terminal parking state and will be re-processed next run. + +- `shipped` — your fix for this `shortId` is in the PR you are opening (or already merged). +- `duplicate` — the exact Sentry issue family is already represented by another ledger row; set `duplicateOf` to that canonical `shortId`. +- `keep_sentry` — confirmed real product bug. Sentry should keep seeing it; do not suppress. Use this whenever you are certain the error is a true bug, including when the only available "fix" would require a new central classifier that hides one. +- `ignored` — you looked, decided no code change is warranted, and recorded why. Use only when at least one applies: + - Dev-environment-only event with no actionable signature (e.g. empty `Error` value at the telemetry reporter). + - The CLI command or feature has been removed; the issue is dead code. + - One-off transient with a single event, no repro, and no useful stack. + - Already addressed by an upstream dependency bump tracked elsewhere. + - Low-signal noise where suppression would risk hiding future regressions, so the correct action is a deliberate no-op. +- `pending_review` — genuine uncertainty about how to classify or fix. Use only when at least one applies: + - Insufficient Sentry data (minified stack, no breadcrumbs, no message context) to identify the boundary. + - Could plausibly be either side of the boundary (e.g. network timeout that could be infra or a retry-logic bug) and you cannot tell without a repro. + - Feature gap rather than an error (CLI throws because a flag combo isn't implemented); needs a product decision. + - Fix would require an architectural refactor (new boundary across multiple layers); bigger than one subagent should decide. + +Anti-patterns (do not do these): + +- Do **not** use `ignored` to "make Sentry quieter". A real bug is `keep_sentry`; anything else either gets a proper fix or one of the explicit `ignored` cases above. +- Do **not** use `pending_review` when you already know it is a real bug — that is `keep_sentry`. +- Do **not** use `pending_review` to defer fixable work you just do not want to do. +- `DESIGN_CHOICES.md` is about how to classify errors so Sentry does not see false positives; it never blocks a real fix. "Principles got in the way" is not a valid reason for `pending_review`. + +## Allowed actions + +- Create branch `` from `main` (do not stack on another subagent's branch). +- Determine and implement a fix at the named call site only — it must solve all your `ShortIds` and respect `DESIGN_CHOICES.md`. +- Update `ledger/.json` files for your `ShortIds` only (see [Ledger rows](#ledger-rows-your-shortids-only)). +- Add a changelog entry under `packages/cli/cli/changes/unreleased/` only if CLI behavior or reporting changes. +- Open exactly one PR whose title includes the `ShortIds` (max three; summarize the family otherwise) and whose description lists every solved `shortId` with a short fix note. + +## Forbidden + +- Editing ledger files outside your `ShortIds` (including `_meta.json`). +- Editing `DESIGN_CHOICES.md`. +- Opening more than one PR, or stacking on another subagent's branch. +- Marking Sentry issues resolved. +- Re-running Sentry fetch or expanding the group. + +## Existing-PR re-check + +If you suspect mid-investigation that an open PR already covers this group, run: + +```bash +.devin/automation/sentry-triage/scripts/find-existing-pr.sh ... +``` + +Inspect the JSON output (`candidates`, `files`, `title`, `body`) and compare against your boundary. If coverage is confirmed, stop without opening a PR and return `skipped: existing PR `. + +## Stop and return findings (no PR) if + +For each case below, set the disposition per [Choosing a disposition](#choosing-a-disposition) and return. These are normal outcomes when the parent's hypothesis does not survive a closer look at the code. + +- The issue is actually a real product bug — set `keep_sentry`. If a "fix" would require a new central classifier that hides the bug, that is still `keep_sentry`; capture the principle conflict in `newPrincipleProposal` for the parent. +- The group turns out to span multiple call sites — leave disposition unchanged for your `ShortIds`; the parent will re-plan. +- An existing PR already covers the group (per script output + diff inspection) — update `prOrIssue` to that PR and return `skipped: existing PR `. +- The `shortId` matches an `ignored` or `pending_review` case from [Choosing a disposition](#choosing-a-disposition) — ledger the row with the matching case as rationale and return. + +## Return value + +At the end, return: + +- `prUrl` — PR URL, or `skipped: ` +- `shortIdsTouched` — exact ledger rows you edited +- `principlesApplied` — which `DESIGN_CHOICES.md` principles you relied on +- `newPrincipleProposal` — optional one-line bullet for the parent to consider (do **not** edit `DESIGN_CHOICES.md` yourself) diff --git a/.devin/automation/sentry-triage/ledger/CLI-10.json b/.devin/automation/sentry-triage/ledger/CLI-10.json new file mode 100644 index 000000000000..4767836ea8e3 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-10.json @@ -0,0 +1,9 @@ +{ + "title": "Container aggregate / sdk generate", + "disposition": "shipped", + "rationale": "ContainerError + aggregate InternalError double-report; use TaskAbortSignal.", + "fixSummary": "ContainerError non-reportable; sdk generate uses TaskAbortSignal instead of aggregate CliError.", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-11.json b/.devin/automation/sentry-triage/ledger/CLI-11.json new file mode 100644 index 000000000000..60c34680a98a --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-11.json @@ -0,0 +1,9 @@ +{ + "title": "Generator not found (generators.yml)", + "disposition": "shipped", + "rationale": "User references unknown generator image; should be ConfigError not InternalError.", + "fixSummary": "Upgrade helper reclassifies to ConfigError.", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "problemSignature": "generators.yml references a generator image or version that is not found in FDR/registry; surfaced as InternalError instead of config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-12.json b/.devin/automation/sentry-triage/ledger/CLI-12.json new file mode 100644 index 000000000000..9caa894d9a0f --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-12.json @@ -0,0 +1,10 @@ +{ + "title": "swagger2openapi unresolved reference", + "disposition": "duplicate", + "rationale": "Same OpenAPI v2 conversion failure family as CLI-13.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-13", + "problemSignature": "User OpenAPI v2 document fails swagger2openapi conversion to v3; conversion error treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-13.json b/.devin/automation/sentry-triage/ledger/CLI-13.json new file mode 100644 index 000000000000..b2645fd1c617 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-13.json @@ -0,0 +1,9 @@ +{ + "title": "swagger2openapi conversion failure", + "disposition": "shipped", + "rationale": "User OpenAPI v2 spec conversion failure should be ParseError.", + "fixSummary": "convertOpenAPIV2ToV3 wraps swagger2openapi as CliError(ParseError).", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "problemSignature": "User OpenAPI v2 document fails swagger2openapi conversion to v3; conversion error treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-14.json b/.devin/automation/sentry-triage/ledger/CLI-14.json new file mode 100644 index 000000000000..4cfd9b538cd0 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-14.json @@ -0,0 +1,10 @@ +{ + "title": "Generator version incompatible with CLI v4", + "disposition": "duplicate", + "rationale": "Same version/config family as CLI-31; current migration code throws VersionError for this path.", + "fixSummary": "—", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-31", + "problemSignature": "Invalid or incompatible generator version reached Sentry instead of VersionError/config guidance." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-15.json b/.devin/automation/sentry-triage/ledger/CLI-15.json new file mode 100644 index 000000000000..bdd4f2df70ec --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-15.json @@ -0,0 +1,9 @@ +{ + "title": "Expected list received object", + "disposition": "shipped", + "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", + "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-17.json b/.devin/automation/sentry-triage/ledger/CLI-17.json new file mode 100644 index 000000000000..bdd4f2df70ec --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-17.json @@ -0,0 +1,9 @@ +{ + "title": "Expected list received object", + "disposition": "shipped", + "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", + "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-18.json b/.devin/automation/sentry-triage/ledger/CLI-18.json new file mode 100644 index 000000000000..7b6be552e552 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-18.json @@ -0,0 +1,9 @@ +{ + "title": "Absolute OpenAPI filepath is not relative", + "disposition": "keep_sentry", + "rationale": "Absolute path reached a relative-path invariant; needs path normalization or boundary validation fix.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-04", + "problemSignature": "OpenAPI or workspace path conversion received an absolute path where a relative path was required; path boundary bug until fixed." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-19.json b/.devin/automation/sentry-triage/ledger/CLI-19.json new file mode 100644 index 000000000000..1aa16468d359 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-19.json @@ -0,0 +1,10 @@ +{ + "title": "Container aggregate / docs publish", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-10; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-10", + "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1A.json b/.devin/automation/sentry-triage/ledger/CLI-1A.json new file mode 100644 index 000000000000..bdd4f2df70ec --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1A.json @@ -0,0 +1,9 @@ +{ + "title": "Expected list received object", + "disposition": "shipped", + "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", + "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1B.json b/.devin/automation/sentry-triage/ledger/CLI-1B.json new file mode 100644 index 000000000000..bdd4f2df70ec --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1B.json @@ -0,0 +1,9 @@ +{ + "title": "Expected list received object", + "disposition": "shipped", + "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", + "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1C.json b/.devin/automation/sentry-triage/ledger/CLI-1C.json new file mode 100644 index 000000000000..bdd4f2df70ec --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1C.json @@ -0,0 +1,9 @@ +{ + "title": "Expected list received object", + "disposition": "shipped", + "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", + "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1D.json b/.devin/automation/sentry-triage/ledger/CLI-1D.json new file mode 100644 index 000000000000..bdd4f2df70ec --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1D.json @@ -0,0 +1,9 @@ +{ + "title": "Expected list received object", + "disposition": "shipped", + "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", + "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1E.json b/.devin/automation/sentry-triage/ledger/CLI-1E.json new file mode 100644 index 000000000000..c32b10bd7a63 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1E.json @@ -0,0 +1,10 @@ +{ + "title": "YAML duplicated mapping key", + "disposition": "duplicate", + "rationale": "Same invalid user YAML parse family as CLI-2E.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2E", + "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1F.json b/.devin/automation/sentry-triage/ledger/CLI-1F.json new file mode 100644 index 000000000000..c07f1485b3cb --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1F.json @@ -0,0 +1,10 @@ +{ + "title": "YAML parse in OpenAPI load", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-2E; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-2E", + "problemSignature": "Invalid YAML while loading a user OpenAPI spec (yaml.load path); parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1G.json b/.devin/automation/sentry-triage/ledger/CLI-1G.json new file mode 100644 index 000000000000..bdd4f2df70ec --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1G.json @@ -0,0 +1,9 @@ +{ + "title": "Expected list received object", + "disposition": "shipped", + "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", + "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1H.json b/.devin/automation/sentry-triage/ledger/CLI-1H.json new file mode 100644 index 000000000000..1f395236b6e2 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1H.json @@ -0,0 +1,9 @@ +{ + "title": "Organization create validation / network", + "disposition": "shipped", + "rationale": "ValidationError/NetworkError passed explicitly instead of InternalError.", + "fixSummary": "createOrganizationIfDoesNotExist explicit codes.", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "problemSignature": "Organization create flow: validation or network failures bubbled up as InternalError instead of explicit validation/network CLI errors." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1J.json b/.devin/automation/sentry-triage/ledger/CLI-1J.json new file mode 100644 index 000000000000..08abef1d6a84 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1J.json @@ -0,0 +1,10 @@ +{ + "title": "Filesystem / errno false positive", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-2X; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1K.json b/.devin/automation/sentry-triage/ledger/CLI-1K.json new file mode 100644 index 000000000000..cba098405e5f --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1K.json @@ -0,0 +1,9 @@ +{ + "title": "GitHub pull request base branch invalid", + "disposition": "shipped", + "rationale": "Remote generation task failures are generator/container task failures, not CLI internal defects.", + "fixSummary": "RemoteTaskHandler maps failed remote tasks to non-reportable ContainerError in current code.", + "prOrIssue": "Already handled in current code; ledger corrected in this PR", + "lastAnalyzed": "2026-05-05", + "problemSignature": "Remote generation task failed because downstream generator/GitHub output rejected user configuration; should be non-reportable task/container failure." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1M.json b/.devin/automation/sentry-triage/ledger/CLI-1M.json new file mode 100644 index 000000000000..c15eb0644442 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1M.json @@ -0,0 +1,9 @@ +{ + "title": "buf generate command failed", + "disposition": "shipped", + "rationale": "buf/protobuf generation is an external local tool boundary; command failures should be user-facing, not internal.", + "fixSummary": "Run buf commands with controlled exit-code handling and map failures to CliError(UserError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-05", + "problemSignature": "buf/protobuf subprocess failed during generation and was reported as InternalError; should be user/tool semantics." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1N.json b/.devin/automation/sentry-triage/ledger/CLI-1N.json new file mode 100644 index 000000000000..2036dca9b008 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1N.json @@ -0,0 +1,10 @@ +{ + "title": "Container execution failed code 1", + "disposition": "duplicate", + "rationale": "Same container/task double-reporting family as CLI-10.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-10", + "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1P.json b/.devin/automation/sentry-triage/ledger/CLI-1P.json new file mode 100644 index 000000000000..2036dca9b008 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1P.json @@ -0,0 +1,10 @@ +{ + "title": "Container execution failed code 1", + "disposition": "duplicate", + "rationale": "Same container/task double-reporting family as CLI-10.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-10", + "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1Q.json b/.devin/automation/sentry-triage/ledger/CLI-1Q.json new file mode 100644 index 000000000000..c7fd8815990d --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1Q.json @@ -0,0 +1,9 @@ +{ + "title": "formatDocs replace is not a function", + "disposition": "keep_sentry", + "rationale": "formatDocs expected a string and received another shape; needs product validation or conversion fix.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-04", + "problemSignature": "IR docs formatting TypeError because docs value was not a string; true product/data-shape bug until fixed." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1R.json b/.devin/automation/sentry-triage/ledger/CLI-1R.json new file mode 100644 index 000000000000..2036dca9b008 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1R.json @@ -0,0 +1,10 @@ +{ + "title": "Container execution failed code 1", + "disposition": "duplicate", + "rationale": "Same container/task double-reporting family as CLI-10.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-10", + "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1S.json b/.devin/automation/sentry-triage/ledger/CLI-1S.json new file mode 100644 index 000000000000..08abef1d6a84 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1S.json @@ -0,0 +1,10 @@ +{ + "title": "Filesystem / errno false positive", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-2X; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1T.json b/.devin/automation/sentry-triage/ledger/CLI-1T.json new file mode 100644 index 000000000000..8545c8d22e2b --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1T.json @@ -0,0 +1,9 @@ +{ + "title": "YAML duplicated mapping key", + "disposition": "shipped", + "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", + "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1V.json b/.devin/automation/sentry-triage/ledger/CLI-1V.json new file mode 100644 index 000000000000..08abef1d6a84 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1V.json @@ -0,0 +1,10 @@ +{ + "title": "Filesystem / errno false positive", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-2X; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1W.json b/.devin/automation/sentry-triage/ledger/CLI-1W.json new file mode 100644 index 000000000000..cbaefdd87106 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1W.json @@ -0,0 +1,9 @@ +{ + "title": "Filesystem / errno / EPIPE-style false positive", + "disposition": "shipped", + "rationale": "User/env or pipe closure; EPIPE listener on TtyAwareLogger plus errno mapping.", + "fixSummary": "PR 15283: process.stdout/stderr error listener + errno mapping.", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "problemSignature": "EPIPE or broken pipe when user pipes CLI stdout/stderr to tools like head/jq; errno-style noise reaching error reporter." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1X.json b/.devin/automation/sentry-triage/ledger/CLI-1X.json new file mode 100644 index 000000000000..08abef1d6a84 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1X.json @@ -0,0 +1,10 @@ +{ + "title": "Filesystem / errno false positive", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-2X; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1Y.json b/.devin/automation/sentry-triage/ledger/CLI-1Y.json new file mode 100644 index 000000000000..795a7629b04a --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1Y.json @@ -0,0 +1,9 @@ +{ + "title": "Missing translations directory validation", + "disposition": "shipped", + "rationale": "Missing translations directory is user docs configuration and was reported without a non-reportable code.", + "fixSummary": "Pass ValidationError when translation locale directories are missing.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Docs translations configuration references a locale whose translations directory is missing; should be validation/config semantics, not InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-1Z.json b/.devin/automation/sentry-triage/ledger/CLI-1Z.json new file mode 100644 index 000000000000..747e03339538 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-1Z.json @@ -0,0 +1,10 @@ +{ + "title": "Failed to parse version: *", + "disposition": "duplicate", + "rationale": "Same invalid-version classification as CLI-31.", + "fixSummary": "—", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-31", + "problemSignature": "Invalid generator or CLI version string (e.g. 0.x, 1.x, *) reached Sentry as plain Error instead of VersionError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-20.json b/.devin/automation/sentry-triage/ledger/CLI-20.json new file mode 100644 index 000000000000..47ad92e8d867 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-20.json @@ -0,0 +1,10 @@ +{ + "title": "Container / task failure reporting", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-10; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-10", + "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-21.json b/.devin/automation/sentry-triage/ledger/CLI-21.json new file mode 100644 index 000000000000..1fab6e7c5d35 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-21.json @@ -0,0 +1,9 @@ +{ + "title": "User post-generation sed command failed", + "disposition": "shipped", + "rationale": "Placeholder replacement failed in generated files because the local sed command could not process user output bytes.", + "fixSummary": "Wrap auto-version placeholder replacement subprocess failures as CliError(UserError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-05", + "problemSignature": "Auto-version placeholder replacement subprocess failed on generated/user files; should be user semantics." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-22.json b/.devin/automation/sentry-triage/ledger/CLI-22.json new file mode 100644 index 000000000000..6cba07e13308 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-22.json @@ -0,0 +1,9 @@ +{ + "title": "Auth0 SSO connection returned HTTP 400", + "disposition": "shipped", + "rationale": "SSO connection resolution 400 is an auth/login boundary failure, not an internal CLI defect.", + "fixSummary": "Map Auth0 SSO resolve HTTP 400 responses to CliError(AuthError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-05", + "problemSignature": "Auth0 login/SSO request returned HTTP 400 and surfaced as AxiosError; should be auth semantics." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-23.json b/.devin/automation/sentry-triage/ledger/CLI-23.json new file mode 100644 index 000000000000..13a78cea94f3 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-23.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected exclamation", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-24.json b/.devin/automation/sentry-triage/ledger/CLI-24.json new file mode 100644 index 000000000000..c07f1485b3cb --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-24.json @@ -0,0 +1,10 @@ +{ + "title": "YAML parse in OpenAPI load", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-2E; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-2E", + "problemSignature": "Invalid YAML while loading a user OpenAPI spec (yaml.load path); parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-25.json b/.devin/automation/sentry-triage/ledger/CLI-25.json new file mode 100644 index 000000000000..82eaf832d2ba --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-25.json @@ -0,0 +1,10 @@ +{ + "title": "Invalid fern.config.json JSON", + "disposition": "duplicate", + "rationale": "Current loadProjectConfig wraps JSON.parse as ParseError; same non-reportable parse/config class as CLI-2Z.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2Z", + "problemSignature": "Schema or configuration parse path for user-authored fern.config.json looked like InternalError instead of parse/config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-26.json b/.devin/automation/sentry-triage/ledger/CLI-26.json new file mode 100644 index 000000000000..47ad92e8d867 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-26.json @@ -0,0 +1,10 @@ +{ + "title": "Container / task failure reporting", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-10; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-10", + "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-27.json b/.devin/automation/sentry-triage/ledger/CLI-27.json new file mode 100644 index 000000000000..108b01f6274c --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-27.json @@ -0,0 +1,9 @@ +{ + "title": "Invalid URL in app preview server", + "disposition": "keep_sentry", + "rationale": "Preview server should validate or normalize the URL source; leave reportable until fixed.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Docs app preview server TypeError Invalid URL; possible product/config boundary bug until fixed." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-28.json b/.devin/automation/sentry-triage/ledger/CLI-28.json new file mode 100644 index 000000000000..c177259f215d --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-28.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected number", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-29.json b/.devin/automation/sentry-triage/ledger/CLI-29.json new file mode 100644 index 000000000000..13a78cea94f3 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-29.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected exclamation", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2A.json b/.devin/automation/sentry-triage/ledger/CLI-2A.json new file mode 100644 index 000000000000..a9827236305c --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2A.json @@ -0,0 +1,9 @@ +{ + "title": "YAML parse in OpenAPI load", + "disposition": "shipped", + "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", + "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-04-21", + "problemSignature": "Invalid YAML while loading a user OpenAPI spec (yaml.load path); parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2B.json b/.devin/automation/sentry-triage/ledger/CLI-2B.json new file mode 100644 index 000000000000..d26afda6e83e --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2B.json @@ -0,0 +1,10 @@ +{ + "title": "Failed to compare versions: Failed to parse version: 0.x", + "disposition": "duplicate", + "rationale": "Same invalid-version classification as CLI-31.", + "fixSummary": "—", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-31", + "problemSignature": "Invalid generator or CLI version string (e.g. 0.x, 1.x, *) reached Sentry as plain Error instead of VersionError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2C.json b/.devin/automation/sentry-triage/ledger/CLI-2C.json new file mode 100644 index 000000000000..08abef1d6a84 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2C.json @@ -0,0 +1,10 @@ +{ + "title": "Filesystem / errno false positive", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-2X; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2D.json b/.devin/automation/sentry-triage/ledger/CLI-2D.json new file mode 100644 index 000000000000..c4b753e52ae2 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2D.json @@ -0,0 +1,9 @@ +{ + "title": "git rm -rf command failed", + "disposition": "shipped", + "rationale": "Generated-output cleanup failed because the user's git worktree/submodule state rejected git rm.", + "fixSummary": "Wrap local generated-output git command failures as CliError(UserError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-05", + "problemSignature": "Git subprocess failure during generated output cleanup reported as InternalError; should be user/worktree semantics." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2E.json b/.devin/automation/sentry-triage/ledger/CLI-2E.json new file mode 100644 index 000000000000..54bd342accf8 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2E.json @@ -0,0 +1,9 @@ +{ + "title": "YAML parse in OpenAPI load", + "disposition": "shipped", + "rationale": "YAMLException from user spec misclassified as InternalError.", + "fixSummary": "Wrap yaml.load in loadOpenAPI; CliError(ParseError) with file/line/column.", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "problemSignature": "Invalid YAML while loading a user OpenAPI spec (yaml.load path); parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2F.json b/.devin/automation/sentry-triage/ledger/CLI-2F.json new file mode 100644 index 000000000000..08abef1d6a84 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2F.json @@ -0,0 +1,10 @@ +{ + "title": "Filesystem / errno false positive", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-2X; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2G.json b/.devin/automation/sentry-triage/ledger/CLI-2G.json new file mode 100644 index 000000000000..47ad92e8d867 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2G.json @@ -0,0 +1,10 @@ +{ + "title": "Container / task failure reporting", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-10; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-10", + "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2H.json b/.devin/automation/sentry-triage/ledger/CLI-2H.json new file mode 100644 index 000000000000..d07f2a18a9b8 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2H.json @@ -0,0 +1,9 @@ +{ + "title": "Local + GitHub project config resolution", + "disposition": "shipped", + "rationale": "User config / workspace path edge case misclassified as InternalError.", + "fixSummary": "Workspace / project config resolution and path handling (see PR #15283 changelog and follow-up #15484 if refined).", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "problemSignature": "Project load with local vs remote (e.g. GitHub) workspace or config paths: user path/config edge misclassified as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2J.json b/.devin/automation/sentry-triage/ledger/CLI-2J.json new file mode 100644 index 000000000000..dd5f7089c551 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2J.json @@ -0,0 +1,9 @@ +{ + "title": "Failed to load generator migrations", + "disposition": "shipped", + "rationale": "Migration loading failed because the user's local migration cache/npm environment could not create its cache directory.", + "fixSummary": "Wrap migration package install/load failures as CliError(EnvironmentError) while preserving true CliErrors.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-05", + "problemSignature": "Generator migrations failed to load due local npm/cache/environment error; should be environment semantics." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2K.json b/.devin/automation/sentry-triage/ledger/CLI-2K.json new file mode 100644 index 000000000000..2be3a1a91556 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2K.json @@ -0,0 +1,10 @@ +{ + "title": "No fern directory found with --id", + "disposition": "duplicate", + "rationale": "Same validation path as CLI-2Y.", + "fixSummary": "—", + "prOrIssue": "Already on main", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2Y", + "problemSignature": "Docs preview delete --id outside a Fern project failed as InternalError instead of ValidationError guidance." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2M.json b/.devin/automation/sentry-triage/ledger/CLI-2M.json new file mode 100644 index 000000000000..8793a48385ba --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2M.json @@ -0,0 +1,10 @@ +{ + "title": "listen EADDRINUSE :::3001", + "disposition": "duplicate", + "rationale": "EADDRINUSE is already covered by the errno environment mapping.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission, port in use) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2N.json b/.devin/automation/sentry-triage/ledger/CLI-2N.json new file mode 100644 index 000000000000..13a78cea94f3 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2N.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected exclamation", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2P.json b/.devin/automation/sentry-triage/ledger/CLI-2P.json new file mode 100644 index 000000000000..039dfc3f5fe2 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2P.json @@ -0,0 +1,10 @@ +{ + "title": "Failed to parse version: 1.x", + "disposition": "duplicate", + "rationale": "Same invalid-version classification as CLI-31.", + "fixSummary": "—", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-31", + "problemSignature": "Invalid generator or CLI version string (e.g. 0.x, 1.x, *) reached Sentry as plain Error instead of VersionError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2Q.json b/.devin/automation/sentry-triage/ledger/CLI-2Q.json new file mode 100644 index 000000000000..736b01fea97c --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2Q.json @@ -0,0 +1,10 @@ +{ + "title": "MDX acorn parse expression", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2R.json b/.devin/automation/sentry-triage/ledger/CLI-2R.json new file mode 100644 index 000000000000..eb9a4a43c9ca --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2R.json @@ -0,0 +1,10 @@ +{ + "title": "MDX import/export parse failure", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2S.json b/.devin/automation/sentry-triage/ledger/CLI-2S.json new file mode 100644 index 000000000000..c7a78a3dbebb --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2S.json @@ -0,0 +1,10 @@ +{ + "title": "listen EADDRINUSE :::3003", + "disposition": "duplicate", + "rationale": "EADDRINUSE is already covered by the errno environment mapping.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission, port in use) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2V.json b/.devin/automation/sentry-triage/ledger/CLI-2V.json new file mode 100644 index 000000000000..218986a6f891 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2V.json @@ -0,0 +1,10 @@ +{ + "title": "MDX / docs image path parse failure", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-2W; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-04-28", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error (often bad `!` or JSX) while resolving doc image paths; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2W.json b/.devin/automation/sentry-triage/ledger/CLI-2W.json new file mode 100644 index 000000000000..b19ddec8d47a --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2W.json @@ -0,0 +1,9 @@ +{ + "title": "59:2: Unexpected character `!` before name", + "disposition": "shipped", + "rationale": "User-authored MDX/acorn parse error surfaced as InternalError; should be ParseError at parseImagePaths call sites.", + "fixSummary": "Wrap parseImagePaths in DocsDefinitionResolver and previewDocs with CliError(ParseError).", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-04-28", + "problemSignature": "Docs or preview path: MDX/acorn parse error (often bad `!` or JSX) while resolving doc image paths; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2X.json b/.devin/automation/sentry-triage/ledger/CLI-2X.json new file mode 100644 index 000000000000..bbb5c9546107 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2X.json @@ -0,0 +1,9 @@ +{ + "title": "Filesystem / errno false positive", + "disposition": "shipped", + "rationale": "User/env errno (e.g. ENOENT) misclassified as InternalError.", + "fixSummary": "ErrnoException mapping in resolveErrorCode; USER_ENVIRONMENT_ERRNOS / NETWORK_ERRNOS.", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2Y.json b/.devin/automation/sentry-triage/ledger/CLI-2Y.json new file mode 100644 index 000000000000..9e9da4e8b655 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2Y.json @@ -0,0 +1,9 @@ +{ + "title": "No fern directory found with --id", + "disposition": "shipped", + "rationale": "Current delete preview flow passes ValidationError for this user invocation problem.", + "fixSummary": "Existing deleteDocsPreview path uses CliError.Code.ValidationError.", + "prOrIssue": "Already on main", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Docs preview delete --id outside a Fern project failed as InternalError instead of ValidationError guidance." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-2Z.json b/.devin/automation/sentry-triage/ledger/CLI-2Z.json new file mode 100644 index 000000000000..425351acbd7c --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-2Z.json @@ -0,0 +1,9 @@ +{ + "title": "Schema validation / minified class names", + "disposition": "shipped", + "rationale": "isSchemaValidationError could not match minified constructor names.", + "fixSummary": "Set this.name on JsonError, ParseError, GeneratorError constructors.", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "problemSignature": "Schema validation path: minified build broke constructor-name-based detection so parse/schema errors looked like InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-30.json b/.devin/automation/sentry-triage/ledger/CLI-30.json new file mode 100644 index 000000000000..9cd3bd1b5ebf --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-30.json @@ -0,0 +1,9 @@ +{ + "title": "YAML multiline key parse failure", + "disposition": "shipped", + "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", + "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-31.json b/.devin/automation/sentry-triage/ledger/CLI-31.json new file mode 100644 index 000000000000..1c346e479db0 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-31.json @@ -0,0 +1,9 @@ +{ + "title": "Failed to parse version: 0.x", + "disposition": "shipped", + "rationale": "Invalid version strings should be reported with explicit VersionError codes at callers/boundaries, not central name-based classification.", + "fixSummary": "Wrap dependency CLI version parsing with VersionError; existing generator-version callers already wrap invalid versions explicitly.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Invalid generator or CLI version string (e.g. 0.x, 1.x, *) reached Sentry as plain Error instead of VersionError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-32.json b/.devin/automation/sentry-triage/ledger/CLI-32.json new file mode 100644 index 000000000000..87ac2126f7ad --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-32.json @@ -0,0 +1,10 @@ +{ + "title": "MDX expected closing br tag", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-33.json b/.devin/automation/sentry-triage/ledger/CLI-33.json new file mode 100644 index 000000000000..d596d0b39f13 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-33.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected asterisk", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-34.json b/.devin/automation/sentry-triage/ledger/CLI-34.json new file mode 100644 index 000000000000..b7991e1c7fcb --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-34.json @@ -0,0 +1,9 @@ +{ + "title": "Python package path not found", + "disposition": "shipped", + "rationale": "Library docs generation failed in the external library-docs service; the CLI should not report service job failures as internal defects.", + "fixSummary": "Map failed library-docs generation statuses to NetworkError instead of InternalError.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-05", + "problemSignature": "Library docs generation failed in an external service job; should be non-reportable service/network semantics." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-35.json b/.devin/automation/sentry-triage/ledger/CLI-35.json new file mode 100644 index 000000000000..5080f2f5e7de --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-35.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected closing slash", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-36.json b/.devin/automation/sentry-triage/ledger/CLI-36.json new file mode 100644 index 000000000000..c4a0cc25ef7a --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-36.json @@ -0,0 +1,10 @@ +{ + "title": "docs navigation schema validation failure", + "disposition": "duplicate", + "rationale": "Same schema validation/minified error-name family as CLI-2Z.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2Z", + "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-37.json b/.devin/automation/sentry-triage/ledger/CLI-37.json new file mode 100644 index 000000000000..d9178ac6951d --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-37.json @@ -0,0 +1,10 @@ +{ + "title": "MDX expected closing Tab tag", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-38.json b/.devin/automation/sentry-triage/ledger/CLI-38.json new file mode 100644 index 000000000000..736b01fea97c --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-38.json @@ -0,0 +1,10 @@ +{ + "title": "MDX acorn parse expression", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-39.json b/.devin/automation/sentry-triage/ledger/CLI-39.json new file mode 100644 index 000000000000..730ade58f5d4 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-39.json @@ -0,0 +1,9 @@ +{ + "title": "Failed to parse GitHub repository", + "disposition": "shipped", + "rationale": "Invalid GitHub repository strings in generators.yml are configuration errors and should be wrapped where generators.yml is converted.", + "fixSummary": "Wrap parseRepository failures in convertGeneratorsConfiguration as CliError(ConfigError); parseRepository still throws a plain Error.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Invalid GitHub repository config string (missing owner/repo or empty path part) surfaced as InternalError instead of ConfigError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3A.json b/.devin/automation/sentry-triage/ledger/CLI-3A.json new file mode 100644 index 000000000000..e4c9e0561123 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3A.json @@ -0,0 +1,9 @@ +{ + "title": "EINTR interrupted syscall in uv_cwd", + "disposition": "shipped", + "rationale": "EINTR is a clear errno-style environment signal, not a Fern product bug.", + "fixSummary": "Add EINTR to USER_ENVIRONMENT_ERRNOS so resolveErrorCode maps it to EnvironmentError.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Filesystem or syscall errno from user environment (EINTR interrupted syscall) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3B.json b/.devin/automation/sentry-triage/ledger/CLI-3B.json new file mode 100644 index 000000000000..4ede6825b8a4 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3B.json @@ -0,0 +1,9 @@ +{ + "title": "Null overlay in applyOverlays", + "disposition": "keep_sentry", + "rationale": "Null overlay TypeError indicates missing validation in workspace overlay handling.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Workspace overlay loading TypeError on null overlay; true validation/product bug until fixed." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3C.json b/.devin/automation/sentry-triage/ledger/CLI-3C.json new file mode 100644 index 000000000000..c10993bf8544 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3C.json @@ -0,0 +1,9 @@ +{ + "title": "Replay resolve failed no-lockfile", + "disposition": "shipped", + "rationale": "Replay resolve no-lockfile is user/worktree state, not an internal CLI defect.", + "fixSummary": "Map fallback replay resolve failures to UserError in both CLI implementations.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Replay resolve failed with no-lockfile or other user/worktree state and was explicitly reported as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3D.json b/.devin/automation/sentry-triage/ledger/CLI-3D.json new file mode 100644 index 000000000000..d684a12b64e5 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3D.json @@ -0,0 +1,9 @@ +{ + "title": "Mintlify import navigation is not iterable", + "disposition": "keep_sentry", + "rationale": "Importer TypeError indicates missing validation or compatibility handling in Mintlify import code.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Mintlify docs import TypeError while reading navigation; true importer robustness bug until fixed." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3E.json b/.devin/automation/sentry-triage/ledger/CLI-3E.json new file mode 100644 index 000000000000..ee0617b0bd6f --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3E.json @@ -0,0 +1,9 @@ +{ + "title": "Internal response property conversion error", + "disposition": "keep_sentry", + "rationale": "IR conversion invariant failure in getObjectPropertyFromResolvedType; this looks like a product bug rather than false-positive noise.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-04", + "problemSignature": "IR conversion invariant failure while resolving response object properties; true product defect until converter behavior is fixed." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3F.json b/.devin/automation/sentry-triage/ledger/CLI-3F.json new file mode 100644 index 000000000000..2ef7ce4ac54c --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3F.json @@ -0,0 +1,9 @@ +{ + "title": "Failed to locate type", + "disposition": "keep_sentry", + "rationale": "Type resolution failed inside IR generation; needs product-level investigation instead of suppression.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-04", + "problemSignature": "IR generation failed to locate a referenced Fern type; possible reference/resolution product bug until validated." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3G.json b/.devin/automation/sentry-triage/ledger/CLI-3G.json new file mode 100644 index 000000000000..8545c8d22e2b --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3G.json @@ -0,0 +1,9 @@ +{ + "title": "YAML duplicated mapping key", + "disposition": "shipped", + "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", + "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3H.json b/.devin/automation/sentry-triage/ledger/CLI-3H.json new file mode 100644 index 000000000000..2ffa44e4ba17 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3H.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected quote in attribute name", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3J.json b/.devin/automation/sentry-triage/ledger/CLI-3J.json new file mode 100644 index 000000000000..44c343d5af78 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3J.json @@ -0,0 +1,9 @@ +{ + "title": "Windows temp OpenAPI filepath is not relative", + "disposition": "keep_sentry", + "rationale": "Absolute/Windows path reached a relative-path invariant; needs path normalization or boundary validation fix.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-04", + "problemSignature": "OpenAPI or workspace path conversion received an absolute path where a relative path was required; path boundary bug until fixed." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3K.json b/.devin/automation/sentry-triage/ledger/CLI-3K.json new file mode 100644 index 000000000000..736b01fea97c --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3K.json @@ -0,0 +1,10 @@ +{ + "title": "MDX acorn parse expression", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3M.json b/.devin/automation/sentry-triage/ledger/CLI-3M.json new file mode 100644 index 000000000000..736b01fea97c --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3M.json @@ -0,0 +1,10 @@ +{ + "title": "MDX acorn parse expression", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3N.json b/.devin/automation/sentry-triage/ledger/CLI-3N.json new file mode 100644 index 000000000000..3af42036ec54 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3N.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected backslash", + "disposition": "duplicate", + "rationale": "Same docs MDX/acorn parse family as CLI-2W.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15484", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2W", + "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3P.json b/.devin/automation/sentry-triage/ledger/CLI-3P.json new file mode 100644 index 000000000000..6d5ff76cab33 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3P.json @@ -0,0 +1,9 @@ +{ + "title": "Global theme fetch returned HTTP 403", + "disposition": "shipped", + "rationale": "Global theme lookup failures are auth/config/network conditions, not internal CLI defects.", + "fixSummary": "Pass ConfigError/NetworkError codes from stitchGlobalTheme failAndThrow sites.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Docs global-theme fetch or asset download failure reported through failAndThrow without a non-reportable config/network code." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3Q.json b/.devin/automation/sentry-triage/ledger/CLI-3Q.json new file mode 100644 index 000000000000..c3cbffe34c20 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3Q.json @@ -0,0 +1,10 @@ +{ + "title": "ENOENT loading docs page", + "disposition": "duplicate", + "rationale": "Same user/environment errno classification as CLI-2X; current code maps ENOENT to EnvironmentError.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3R.json b/.devin/automation/sentry-triage/ledger/CLI-3R.json new file mode 100644 index 000000000000..5c58217c37d4 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3R.json @@ -0,0 +1,9 @@ +{ + "title": "EPERM mkdir unhandled rejection", + "disposition": "shipped", + "rationale": "Posthog analytics ID persistence is optional user-environment state; UserPosthogManager now handles filesystem failures at the telemetry boundary instead of letting EPERM escape as an unhandled rejection.", + "fixSummary": "Fallback to an in-memory Posthog distinct ID when persisted ID storage cannot be read or written.", + "prOrIssue": "https://github.com/fern-api/fern/pull/15941", + "lastAnalyzed": "2026-05-16", + "problemSignature": "Unhandled rejection with EPERM errno from background telemetry bypassed resolveErrorCode and reached Sentry as raw exception." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3S.json b/.devin/automation/sentry-triage/ledger/CLI-3S.json new file mode 100644 index 000000000000..b04bbe1bbf2d --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3S.json @@ -0,0 +1,9 @@ +{ + "title": "ENOMEM open user spec file", + "disposition": "shipped", + "rationale": "ENOMEM is a user-environment errno (out of memory opening file); added to USER_ENVIRONMENT_ERRNOS.", + "fixSummary": "Add ENOMEM to USER_ENVIRONMENT_ERRNOS in resolveErrorCode.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "problemSignature": "Filesystem errno ENOMEM while opening user spec file surfaced as InternalError instead of EnvironmentError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3T.json b/.devin/automation/sentry-triage/ledger/CLI-3T.json new file mode 100644 index 000000000000..3cbbad53796c --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3T.json @@ -0,0 +1,9 @@ +{ + "title": "YAML bad nested page indentation", + "disposition": "shipped", + "rationale": "User-authored docs navigation YAML had malformed nested page indentation and previously surfaced as an InternalError before the navigation YAML boundary handled parser failures.", + "fixSummary": "Wrap product/version navigation yaml.load calls in getNavigationConfiguration/getVersionedNavigationConfiguration as CliError(ParseError).", + "prOrIssue": "https://github.com/fern-api/fern/pull/15937", + "lastAnalyzed": "2026-05-16", + "problemSignature": "YAMLException while parsing docs.yml or referenced docs navigation YAML; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3V.json b/.devin/automation/sentry-triage/ledger/CLI-3V.json new file mode 100644 index 000000000000..430ab586f7f1 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3V.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected character before name", + "disposition": "shipped", + "rationale": "Same MDX parse boundary family as CLI-44.", + "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-44", + "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3W.json b/.devin/automation/sentry-triage/ledger/CLI-3W.json new file mode 100644 index 000000000000..5a7b818f4c4b --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3W.json @@ -0,0 +1,10 @@ +{ + "title": "ENOENT reading OpenAPI spec", + "disposition": "duplicate", + "rationale": "ENOENT is already mapped to EnvironmentError by the errno classification shipped in CLI-2X.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem errno ENOENT from user environment surfaced as InternalError instead of EnvironmentError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3X.json b/.devin/automation/sentry-triage/ledger/CLI-3X.json new file mode 100644 index 000000000000..5a7b818f4c4b --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3X.json @@ -0,0 +1,10 @@ +{ + "title": "ENOENT reading OpenAPI spec", + "disposition": "duplicate", + "rationale": "ENOENT is already mapped to EnvironmentError by the errno classification shipped in CLI-2X.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem errno ENOENT from user environment surfaced as InternalError instead of EnvironmentError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3Y.json b/.devin/automation/sentry-triage/ledger/CLI-3Y.json new file mode 100644 index 000000000000..f164ae8ba963 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3Y.json @@ -0,0 +1,9 @@ +{ + "title": "spawn xdg-open ENOENT uncaught exception", + "disposition": "keep_sentry", + "rationale": "ENOENT from spawn xdg-open escapes normal error handling as uncaught exception; reaches Sentry via onUncaughtExceptionIntegration. Needs boundary-level wrapping at the spawn call site.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until boundary-level fix", + "lastAnalyzed": "2026-05-16", + "problemSignature": "Uncaught exception with ENOENT errno from spawn xdg-open bypassed resolveErrorCode and reached Sentry as raw exception." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-3Z.json b/.devin/automation/sentry-triage/ledger/CLI-3Z.json new file mode 100644 index 000000000000..e2a1ca5015ee --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-3Z.json @@ -0,0 +1,9 @@ +{ + "title": "YAML bad mapping indentation", + "disposition": "shipped", + "rationale": "User-authored docs.yml had malformed indentation around a docs config field and previously surfaced as an InternalError from the docs YAML load boundary.", + "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", + "prOrIssue": "https://github.com/fern-api/fern/pull/15698", + "lastAnalyzed": "2026-05-16", + "problemSignature": "YAMLException while parsing docs.yml or referenced docs navigation YAML; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-40.json b/.devin/automation/sentry-triage/ledger/CLI-40.json new file mode 100644 index 000000000000..db4faf264adc --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-40.json @@ -0,0 +1,9 @@ +{ + "title": "Received 429 Too Many Requests", + "disposition": "shipped", + "rationale": "HTTP 429 rate-limit from remote generation service. retryWithRateLimit now throws CliError(NetworkError) on exhaustion, and generateOne passes error object through failWithoutThrowing so resolveErrorCode propagates the code.", + "fixSummary": "Change retryWithRateLimit exhaustion code from InternalError to NetworkError; pass error object in generateOne catch to failWithoutThrowing.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "problemSignature": "TooManyRequestsError from retryWithRateLimit reaching Sentry because failWithoutThrowing(message) lost the error object and code." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-41.json b/.devin/automation/sentry-triage/ledger/CLI-41.json new file mode 100644 index 000000000000..19681d2327cc --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-41.json @@ -0,0 +1,9 @@ +{ + "title": "Docs preview server failed to start: Server process exited with code 1", + "disposition": "shipped", + "rationale": "Next.js subprocess failing to start is an environment issue (port conflict, missing deps, etc.), not a Fern bug. Reclassified from InternalError to EnvironmentError.", + "fixSummary": "Change failAndThrow code from InternalError to EnvironmentError in runAppPreviewServer.ts.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "problemSignature": "startNextJsServer failure classified as InternalError instead of EnvironmentError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-42.json b/.devin/automation/sentry-triage/ledger/CLI-42.json new file mode 100644 index 000000000000..e71e9ebdb8c2 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-42.json @@ -0,0 +1,9 @@ +{ + "title": "Generic Error (minified, no actionable signature)", + "disposition": "ignored", + "rationale": "Single event with generic Error and no useful stack trace or context after minification; no actionable product signature to fix or suppress safely.", + "fixSummary": "—", + "prOrIssue": "Ignored after event-level review", + "lastAnalyzed": "2026-05-16", + "problemSignature": "Minified generic Error with no actionable stack trace or context." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-43.json b/.devin/automation/sentry-triage/ledger/CLI-43.json new file mode 100644 index 000000000000..271d06e656fc --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-43.json @@ -0,0 +1,9 @@ +{ + "title": "TypeError: fetch failed (docs resolver)", + "disposition": "shipped", + "rationale": "Undici TypeError: fetch failed escapes as unhandled error; wrapped fetch calls at boundary with try/catch → NetworkError in uploadDynamicIRs, SourceUploader, and uploadDynamicIRForSdkGeneration.", + "fixSummary": "Wrap bare fetch() calls in uploadDynamicIRs, SourceUploader.uploadSource, and uploadDynamicIRForSdkGeneration with try/catch that classifies network failures as NetworkError at the call site.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "problemSignature": "Undici TypeError fetch failed during docs resolution surfaced as InternalError instead of NetworkError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-44.json b/.devin/automation/sentry-triage/ledger/CLI-44.json new file mode 100644 index 000000000000..0f6dd2ac9530 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-44.json @@ -0,0 +1,9 @@ +{ + "title": "MDX expected closing Tip tag", + "disposition": "shipped", + "rationale": "MDX parse error from user-authored docs content. replaceImagePathsAndUrls in DocsDefinitionResolver and previewDocs now wrapped with try/catch that converts to CliError(ParseError).", + "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-45.json b/.devin/automation/sentry-triage/ledger/CLI-45.json new file mode 100644 index 000000000000..3e4d66d519a1 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-45.json @@ -0,0 +1,10 @@ +{ + "title": "MDX expected closing div tag", + "disposition": "shipped", + "rationale": "Same MDX parse boundary family as CLI-44.", + "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-44", + "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-46.json b/.devin/automation/sentry-triage/ledger/CLI-46.json new file mode 100644 index 000000000000..d8745c6bdd2f --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-46.json @@ -0,0 +1,9 @@ +{ + "title": "Filepath is not relative (Linux absolute path)", + "disposition": "keep_sentry", + "rationale": "Absolute path reached a relative-path invariant; same family as CLI-3J and CLI-18.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-16", + "problemSignature": "OpenAPI or workspace path conversion received an absolute path where a relative path was required; path boundary bug." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-47.json b/.devin/automation/sentry-triage/ledger/CLI-47.json new file mode 100644 index 000000000000..8eb785043ba1 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-47.json @@ -0,0 +1,10 @@ +{ + "title": "MDX expected closing img tag", + "disposition": "shipped", + "rationale": "Same MDX parse boundary family as CLI-44.", + "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-44", + "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-48.json b/.devin/automation/sentry-triage/ledger/CLI-48.json new file mode 100644 index 000000000000..430ab586f7f1 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-48.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected character before name", + "disposition": "shipped", + "rationale": "Same MDX parse boundary family as CLI-44.", + "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-44", + "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-49.json b/.devin/automation/sentry-triage/ledger/CLI-49.json new file mode 100644 index 000000000000..ff673b7ee792 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-49.json @@ -0,0 +1,9 @@ +{ + "title": "YAML multiline key parse failure", + "disposition": "shipped", + "rationale": "User-authored version navigation YAML failed during getVersionedNavigationConfiguration and previously surfaced as an InternalError.", + "fixSummary": "Wrap product/version navigation yaml.load calls in getNavigationConfiguration/getVersionedNavigationConfiguration as CliError(ParseError).", + "prOrIssue": "https://github.com/fern-api/fern/pull/15937", + "lastAnalyzed": "2026-05-16", + "problemSignature": "YAMLException while parsing docs.yml or referenced docs navigation YAML; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4A.json b/.devin/automation/sentry-triage/ledger/CLI-4A.json new file mode 100644 index 000000000000..fefe1e09f5c1 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4A.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected quote in attribute name", + "disposition": "shipped", + "rationale": "Same MDX parse boundary family as CLI-44.", + "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-44", + "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4B.json b/.devin/automation/sentry-triage/ledger/CLI-4B.json new file mode 100644 index 000000000000..7f8891dbdc89 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4B.json @@ -0,0 +1,9 @@ +{ + "title": "Failed to load generator migrations for fernapi/fern-typescript-sdk", + "disposition": "shipped", + "rationale": "npm install failure (EACCES on rename) in migration cache. loadMigrationModule already wraps non-CliError exceptions as CliError(EnvironmentError) at the boundary.", + "fixSummary": "Already fixed: loadMigrationModule catch block wraps with EnvironmentError.", + "prOrIssue": "This PR (ledger only)", + "lastAnalyzed": "2026-05-16", + "problemSignature": "npm cache rename EACCES inside ensureMigrationsInstalled, caught and wrapped as EnvironmentError by loadMigrationModule." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4C.json b/.devin/automation/sentry-triage/ledger/CLI-4C.json new file mode 100644 index 000000000000..82ef8ebbbf50 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4C.json @@ -0,0 +1,9 @@ +{ + "title": "Endpoint not found in readme config", + "disposition": "keep_sentry", + "rationale": "convertReadmeConfig could not match an endpoint; either user readme config is wrong or the endpoint matcher has a product bug.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-16", + "problemSignature": "IR generation readme config endpoint matcher failed to locate a matching endpoint; needs product investigation." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4D.json b/.devin/automation/sentry-triage/ledger/CLI-4D.json new file mode 100644 index 000000000000..e3889b161d6d --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4D.json @@ -0,0 +1,9 @@ +{ + "title": "YAML bad sequence indentation", + "disposition": "shipped", + "rationale": "User-authored docs.yml had malformed indentation and previously surfaced as an InternalError while loading raw docs configuration.", + "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", + "prOrIssue": "https://github.com/fern-api/fern/pull/15698", + "lastAnalyzed": "2026-05-16", + "problemSignature": "YAMLException while parsing docs.yml or referenced docs navigation YAML; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4E.json b/.devin/automation/sentry-triage/ledger/CLI-4E.json new file mode 100644 index 000000000000..fefe1e09f5c1 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4E.json @@ -0,0 +1,10 @@ +{ + "title": "MDX unexpected quote in attribute name", + "disposition": "shipped", + "rationale": "Same MDX parse boundary family as CLI-44.", + "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-44", + "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4F.json b/.devin/automation/sentry-triage/ledger/CLI-4F.json new file mode 100644 index 000000000000..eb5cef5a1c8d --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4F.json @@ -0,0 +1,9 @@ +{ + "title": "Global theme \"dark\" not found for org \"asi\"", + "disposition": "shipped", + "rationale": "User references a global theme that does not exist. stitchGlobalTheme already passes ConfigError to failAndThrow for 404 and NOT_FOUND responses.", + "fixSummary": "Already fixed: failAndThrow passes { code: ConfigError } for theme-not-found.", + "prOrIssue": "This PR (ledger only)", + "lastAnalyzed": "2026-05-16", + "problemSignature": "Global theme 404/NOT_FOUND classified as ConfigError by stitchGlobalTheme." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4G.json b/.devin/automation/sentry-triage/ledger/CLI-4G.json new file mode 100644 index 000000000000..61ec8317c1dc --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4G.json @@ -0,0 +1,9 @@ +{ + "title": "Endpoint not found in readme config", + "disposition": "keep_sentry", + "rationale": "Same readme config endpoint resolution family as CLI-4C.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-16", + "problemSignature": "IR generation readme config endpoint matcher failed to locate a matching endpoint; needs product investigation." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4H.json b/.devin/automation/sentry-triage/ledger/CLI-4H.json new file mode 100644 index 000000000000..ee06d7a7f046 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4H.json @@ -0,0 +1,10 @@ +{ + "title": "TypeError: fetch failed (source upload)", + "disposition": "duplicate", + "rationale": "Same boundary-level fetch wrapping as CLI-43; SourceUploader.uploadSource now catches TypeError and classifies as NetworkError.", + "fixSummary": "—", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-43", + "problemSignature": "Undici TypeError fetch failed during source upload surfaced as InternalError instead of NetworkError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4J.json b/.devin/automation/sentry-triage/ledger/CLI-4J.json new file mode 100644 index 000000000000..13e2bdeb705d --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4J.json @@ -0,0 +1,9 @@ +{ + "title": "Unsupported OpenAPI version 3.2.0", + "disposition": "keep_sentry", + "rationale": "Redocly threw for unsupported OpenAPI 3.2.0; the CLI should either support it or classify as ParseError at the OpenAPI loading boundary. Needs product decision.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-16", + "problemSignature": "Redocly rejected user OpenAPI spec version 3.2.0 as unsupported; needs product decision on classification or support." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4K.json b/.devin/automation/sentry-triage/ledger/CLI-4K.json new file mode 100644 index 000000000000..65ae1feedda5 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4K.json @@ -0,0 +1,10 @@ +{ + "title": "MDX acorn parse expression", + "disposition": "shipped", + "rationale": "Same MDX parse boundary family as CLI-44.", + "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-44", + "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4M.json b/.devin/automation/sentry-triage/ledger/CLI-4M.json new file mode 100644 index 000000000000..d73574b0887f --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4M.json @@ -0,0 +1,9 @@ +{ + "title": "EPIPE write uncaught exception", + "disposition": "shipped", + "rationale": "EPIPE from cli-v2 stdout/stderr write escaped as an uncaught stream error; the PR swallows explicit EPIPE at the terminal logger I/O boundary while rethrowing other stream errors.", + "fixSummary": "Added cli-v2 stdout/stderr EPIPE stream error handling in TtyAwareLogger.", + "prOrIssue": "https://github.com/fern-api/fern/pull/15944", + "lastAnalyzed": "2026-05-16", + "problemSignature": "Uncaught exception with EPIPE errno from stream write bypassed resolveErrorCode and reached Sentry as raw exception." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4N.json b/.devin/automation/sentry-triage/ledger/CLI-4N.json new file mode 100644 index 000000000000..73d7be81804b --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4N.json @@ -0,0 +1,9 @@ +{ + "title": "ENOTEMPTY rmdir generated output", + "disposition": "shipped", + "rationale": "ENOTEMPTY is a user-environment errno (directory not empty during rmdir of generated output); added to USER_ENVIRONMENT_ERRNOS.", + "fixSummary": "Add ENOTEMPTY to USER_ENVIRONMENT_ERRNOS in resolveErrorCode.", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "problemSignature": "Filesystem errno ENOTEMPTY during generated-output cleanup surfaced as InternalError instead of EnvironmentError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4P.json b/.devin/automation/sentry-triage/ledger/CLI-4P.json new file mode 100644 index 000000000000..d05bfdd3fc74 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4P.json @@ -0,0 +1,9 @@ +{ + "title": "Undefined server name in IR-to-FDR conversion", + "problemSignature": "IR-to-FDR converter encountered undefined server name at endpoint; server URL mapping product bug.", + "disposition": "shipped", + "rationale": "Undefined endpoint server references come from user-authored API environment configuration and should surface as validation errors at the IR-to-FDR boundary, not as reportable internal conversion failures.", + "fixSummary": "Reclassified undefined REST endpoint server references in IR-to-FDR conversion as non-reportable validation errors.", + "prOrIssue": "https://github.com/fern-api/fern/pull/15943", + "lastAnalyzed": "2026-05-16" +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4Q.json b/.devin/automation/sentry-triage/ledger/CLI-4Q.json new file mode 100644 index 000000000000..821c86367e2c --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4Q.json @@ -0,0 +1,9 @@ +{ + "title": "Failed to fetch global theme \"nvidia\": HTTP 403", + "disposition": "shipped", + "rationale": "HTTP 403 from FDR when fetching a global theme the org lacks access to. stitchGlobalTheme already passes ConfigError to failAndThrow for non-ok HTTP responses.", + "fixSummary": "Already fixed: failAndThrow passes { code: ConfigError } for non-ok HTTP status.", + "prOrIssue": "This PR (ledger only)", + "lastAnalyzed": "2026-05-16", + "problemSignature": "Global theme HTTP 403 classified as ConfigError by stitchGlobalTheme." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4R.json b/.devin/automation/sentry-triage/ledger/CLI-4R.json new file mode 100644 index 000000000000..05e5e35a64d4 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4R.json @@ -0,0 +1,10 @@ +{ + "title": "ENOTEMPTY rmdir generated Java SDK", + "disposition": "duplicate", + "rationale": "Same ENOTEMPTY errno classification as CLI-4N.", + "fixSummary": "—", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-4N", + "problemSignature": "Filesystem errno ENOTEMPTY during generated-output cleanup surfaced as InternalError instead of EnvironmentError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4S.json b/.devin/automation/sentry-triage/ledger/CLI-4S.json new file mode 100644 index 000000000000..05e5e35a64d4 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4S.json @@ -0,0 +1,10 @@ +{ + "title": "ENOTEMPTY rmdir generated Java SDK", + "disposition": "duplicate", + "rationale": "Same ENOTEMPTY errno classification as CLI-4N.", + "fixSummary": "—", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-16", + "duplicateOf": "CLI-4N", + "problemSignature": "Filesystem errno ENOTEMPTY during generated-output cleanup surfaced as InternalError instead of EnvironmentError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4T.json b/.devin/automation/sentry-triage/ledger/CLI-4T.json new file mode 100644 index 000000000000..5f147095a197 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4T.json @@ -0,0 +1,9 @@ +{ + "title": "Failed to find PronunciationItem", + "disposition": "keep_sentry", + "rationale": "FDR SDK example generation failed to resolve a type; this is a product/SDK bug in generateEndpointExampleCall.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-16", + "problemSignature": "FDR SDK example generation failed to locate a referenced type during endpoint example call generation; true product bug." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4V.json b/.devin/automation/sentry-triage/ledger/CLI-4V.json new file mode 100644 index 000000000000..d473c8af08c6 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4V.json @@ -0,0 +1,9 @@ +{ + "title": "Cannot find declaration of type in IR generator", + "disposition": "keep_sentry", + "rationale": "IR generator TypeResolver could not find a declared type; this is a product/resolution bug.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-16", + "problemSignature": "IR generation TypeResolver failed to locate a declared type; possible reference/resolution product bug." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4W.json b/.devin/automation/sentry-triage/ledger/CLI-4W.json new file mode 100644 index 000000000000..b98a813a4b06 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4W.json @@ -0,0 +1,9 @@ +{ + "title": "Failed to find NorthErrorType", + "disposition": "keep_sentry", + "rationale": "FDR SDK example generation failed to resolve a type; same product bug family as CLI-4T.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-16", + "problemSignature": "FDR SDK example generation failed to locate a referenced type during endpoint example call generation; true product bug." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4X.json b/.devin/automation/sentry-triage/ledger/CLI-4X.json new file mode 100644 index 000000000000..1177eeed7896 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4X.json @@ -0,0 +1,9 @@ +{ + "title": "Failed to find Error type", + "disposition": "keep_sentry", + "rationale": "FDR SDK example generation failed to resolve a type; same product bug family as CLI-4T.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until product fix", + "lastAnalyzed": "2026-05-16", + "problemSignature": "FDR SDK example generation failed to locate a referenced type during endpoint example call generation; true product bug." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4Y.json b/.devin/automation/sentry-triage/ledger/CLI-4Y.json new file mode 100644 index 000000000000..474e343807ce --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4Y.json @@ -0,0 +1,9 @@ +{ + "title": "YAML document separator parse failure", + "disposition": "shipped", + "rationale": "User-authored docs navigation YAML failed while loading a product/version navigation file and previously surfaced as an InternalError.", + "fixSummary": "Wrap product/version navigation yaml.load calls in getNavigationConfiguration/getVersionedNavigationConfiguration as CliError(ParseError).", + "prOrIssue": "https://github.com/fern-api/fern/pull/15937", + "lastAnalyzed": "2026-05-16", + "problemSignature": "YAMLException while parsing docs.yml or referenced docs navigation YAML; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-4Z.json b/.devin/automation/sentry-triage/ledger/CLI-4Z.json new file mode 100644 index 000000000000..523b0494bfed --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-4Z.json @@ -0,0 +1,9 @@ +{ + "title": "ENOSPC file watcher unhandled rejection", + "disposition": "keep_sentry", + "rationale": "ENOSPC from file watcher escapes normal error handling as unhandled rejection; reaches Sentry via onUnhandledRejectionIntegration. Needs boundary-level wrapping at the file watcher call site.", + "fixSummary": "—", + "prOrIssue": "Keep in Sentry until boundary-level fix", + "lastAnalyzed": "2026-05-16", + "problemSignature": "Unhandled rejection with ENOSPC errno from file watcher bypassed resolveErrorCode and reached Sentry as raw exception." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-8.json b/.devin/automation/sentry-triage/ledger/CLI-8.json new file mode 100644 index 000000000000..22a2988a284a --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-8.json @@ -0,0 +1,10 @@ +{ + "title": "docs.yml navbar-links schema validation failure", + "disposition": "duplicate", + "rationale": "Same schema validation/minified error-name family as CLI-2Z.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2Z", + "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-C.json b/.devin/automation/sentry-triage/ledger/CLI-C.json new file mode 100644 index 000000000000..d907ab47a8bf --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-C.json @@ -0,0 +1,9 @@ +{ + "title": "stream-json parser expected comma", + "disposition": "shipped", + "rationale": "The stream-json parser failure occurs while reading user-provided IR for diff; current code wraps it as ParseError at that boundary.", + "fixSummary": "diff command catches streamObjectFromFile failures and reports CliError(ParseError).", + "prOrIssue": "Already handled in current code; ledger corrected in this PR", + "lastAnalyzed": "2026-05-05", + "problemSignature": "stream-json parser failed on malformed user-provided IR input; should be parse semantics at diff boundary." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-D.json b/.devin/automation/sentry-triage/ledger/CLI-D.json new file mode 100644 index 000000000000..37ca83982376 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-D.json @@ -0,0 +1,9 @@ +{ + "title": "Invalid BAML provider bedrock", + "disposition": "shipped", + "rationale": "Unsupported BAML provider comes from user AI configuration in generators.yml.", + "fixSummary": "Wrap BAML client setup failures at the local-generation AI config boundary as CliError(ConfigError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-05", + "problemSignature": "AI configuration selected an unsupported BAML provider and surfaced as InternalError; should be config semantics." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-E.json b/.devin/automation/sentry-triage/ledger/CLI-E.json new file mode 100644 index 000000000000..cfe9b126599e --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-E.json @@ -0,0 +1,10 @@ +{ + "title": "Schema validation / minified class names", + "disposition": "duplicate", + "rationale": "Same fix narrative as CLI-2Z; one PR covered both.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "duplicateOf": "CLI-2Z", + "problemSignature": "Schema validation path: minified build broke constructor-name-based detection so parse/schema errors looked like InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-F.json b/.devin/automation/sentry-triage/ledger/CLI-F.json new file mode 100644 index 000000000000..5fc3c949b718 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-F.json @@ -0,0 +1,10 @@ +{ + "title": "Unexpected key package-name", + "disposition": "duplicate", + "rationale": "Same schema validation/minified error-name family as CLI-2Z.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2Z", + "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-G.json b/.devin/automation/sentry-triage/ledger/CLI-G.json new file mode 100644 index 000000000000..1cfb9d279b71 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-G.json @@ -0,0 +1,9 @@ +{ + "title": "YAML bad indentation", + "disposition": "shipped", + "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", + "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-H.json b/.devin/automation/sentry-triage/ledger/CLI-H.json new file mode 100644 index 000000000000..fd891bd81e15 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-H.json @@ -0,0 +1,9 @@ +{ + "title": "ExitPromptError after SIGINT", + "disposition": "shipped", + "rationale": "Prompt SIGINT is a user abort; current CliContext prompt wrappers convert ExitPromptError to TaskAbortSignal before reporting.", + "fixSummary": "Handle ExitPromptError at prompt boundaries and abort without Sentry reporting.", + "prOrIssue": "Already handled in current code; ledger corrected in this PR", + "lastAnalyzed": "2026-05-05", + "problemSignature": "Interactive prompt cancellation via SIGINT surfaced as Sentry InternalError instead of a non-reportable user abort." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-J.json b/.devin/automation/sentry-triage/ledger/CLI-J.json new file mode 100644 index 000000000000..dc613a770a51 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-J.json @@ -0,0 +1,9 @@ +{ + "title": "Rerun CLI at version failure", + "disposition": "shipped", + "rationale": "Spurious InternalError on rerun failure; TaskAbortSignal.", + "fixSummary": "rerunFernCliAtVersion uses TaskAbortSignal.", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-04-21", + "problemSignature": "CLI rerun-at-version path failed and reported misleading InternalError instead of controlled task abort." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-K.json b/.devin/automation/sentry-triage/ledger/CLI-K.json new file mode 100644 index 000000000000..a50e093035be --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-K.json @@ -0,0 +1,9 @@ +{ + "title": "Package fern-api could not be found", + "disposition": "shipped", + "rationale": "The latest-version package lookup is now handled at the CLI bootstrap boundary and converted to a non-reportable NetworkError instead of escaping as PackageNotFoundError.", + "fixSummary": "Wrap latest CLI package lookup failures in CliError.Code.NetworkError.", + "prOrIssue": "https://github.com/fern-api/fern/pull/14753", + "lastAnalyzed": "2026-05-16", + "problemSignature": "CLI package lookup/bootstrap failed because package could not be found; environment/package boundary needs investigation." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-N.json b/.devin/automation/sentry-triage/ledger/CLI-N.json new file mode 100644 index 000000000000..6a9efa127184 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-N.json @@ -0,0 +1,9 @@ +{ + "title": "YAML bad sequence indentation", + "disposition": "shipped", + "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", + "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-P.json b/.devin/automation/sentry-triage/ledger/CLI-P.json new file mode 100644 index 000000000000..1cfb9d279b71 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-P.json @@ -0,0 +1,9 @@ +{ + "title": "YAML bad indentation", + "disposition": "shipped", + "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", + "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-Q.json b/.devin/automation/sentry-triage/ledger/CLI-Q.json new file mode 100644 index 000000000000..8545c8d22e2b --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-Q.json @@ -0,0 +1,9 @@ +{ + "title": "YAML duplicated mapping key", + "disposition": "shipped", + "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", + "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", + "prOrIssue": "This PR", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-Y.json b/.devin/automation/sentry-triage/ledger/CLI-Y.json new file mode 100644 index 000000000000..4bfbdcb5d29f --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-Y.json @@ -0,0 +1,9 @@ +{ + "title": "Generic empty Error in development", + "disposition": "ignored", + "rationale": "Only visible event is development environment with empty Error value grouped at telemetry reporter; no product signature to fix or suppress safely.", + "fixSummary": "—", + "prOrIssue": "Ignored after event-level review", + "lastAnalyzed": "2026-05-04", + "problemSignature": "Development-only empty Error grouped at reportError/capture path with no actionable product signature." +} diff --git a/.devin/automation/sentry-triage/ledger/CLI-Z.json b/.devin/automation/sentry-triage/ledger/CLI-Z.json new file mode 100644 index 000000000000..e4b22bef3c49 --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/CLI-Z.json @@ -0,0 +1,10 @@ +{ + "title": "ENOSPC no space left on device", + "disposition": "duplicate", + "rationale": "ENOSPC is already covered by the errno environment mapping.", + "fixSummary": "—", + "prOrIssue": "https://github.com/fern-api/fern/pull/15283", + "lastAnalyzed": "2026-05-04", + "duplicateOf": "CLI-2X", + "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission, disk full) surfaced as InternalError instead of environment-style CLI error." +} diff --git a/.devin/automation/sentry-triage/ledger/_meta.json b/.devin/automation/sentry-triage/ledger/_meta.json new file mode 100644 index 000000000000..2a6b900b1f7e --- /dev/null +++ b/.devin/automation/sentry-triage/ledger/_meta.json @@ -0,0 +1,4 @@ +{ + "schemaVersion": 3, + "project": "buildwithfern/cli" +} diff --git a/.devin/automation/sentry-triage/scripts/find-existing-pr.sh b/.devin/automation/sentry-triage/scripts/find-existing-pr.sh new file mode 100755 index 000000000000..7714c553a2eb --- /dev/null +++ b/.devin/automation/sentry-triage/scripts/find-existing-pr.sh @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +# find-existing-pr.sh — deterministic existing-PR lookup for Sentry triage shortIds. +# +# Usage: find-existing-pr.sh CLI-44 CLI-4T ... +# Env: +# FERN_REPO GitHub repo (default: fern-api/fern) +# LEDGER_DIR Path to ledger directory (default: .devin/automation/sentry-triage/ledger) +# +# Requires: gh, jq +# Stdout: JSON array (one object per input shortId) +# Stderr: progress messages +# Exit: 0 on success; non-zero on missing tools or gh auth failure + +set -euo pipefail + +FERN_REPO="${FERN_REPO:-fern-api/fern}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LEDGER_DIR="${LEDGER_DIR:-${SCRIPT_DIR}/../ledger}" + +if ! command -v gh >/dev/null 2>&1; then + echo "error: gh is required but not found in PATH" >&2 + exit 1 +fi +if ! command -v jq >/dev/null 2>&1; then + echo "error: jq is required but not found in PATH" >&2 + exit 1 +fi +if [[ $# -lt 1 ]]; then + echo "usage: find-existing-pr.sh [shortId ...]" >&2 + exit 1 +fi + +if ! gh auth status >/dev/null 2>&1; then + echo "error: gh is not authenticated (run: gh auth login)" >&2 + exit 1 +fi + +if [[ ! -d "${LEDGER_DIR}" ]]; then + echo "error: ledger directory not found at ${LEDGER_DIR}" >&2 + exit 1 +fi + +# --- helpers ----------------------------------------------------------------- + +# Normalize shortId for branch matching (CLI-44 -> cli-44) +normalize_short_id_kebab() { + echo "$1" | tr '[:upper:]' '[:lower:]' +} + +# Fetch PR metadata by number or URL; prints JSON object or empty on failure +fetch_pr_metadata() { + local ref="$1" + local source="$2" + gh pr view "${ref}" --repo "${FERN_REPO}" \ + --json state,isDraft,mergedAt,url,title,body,headRefName,baseRefName,number,files 2>/dev/null \ + | jq --arg source "${source}" ' + { + source: $source, + url: .url, + number: .number, + state: .state, + isDraft: .isDraft, + mergedAt: .mergedAt, + headRefName: .headRefName, + baseRefName: .baseRefName, + title: .title, + body: .body, + files: [(.files // [])[] | .path] + } + ' 2>/dev/null || true +} + +# Merge candidate into accumulator array (dedupe by url) +add_candidate() { + local candidate="$1" + if [[ -z "${candidate}" || "${candidate}" == "null" ]]; then + return + fi + local url + url="$(echo "${candidate}" | jq -r '.url // empty')" + if [[ -z "${url}" ]]; then + return + fi + # Skip if url already present + if echo "${CANDIDATES_JSON}" | jq -e --arg url "${url}" 'map(select(.url == $url)) | length > 0' >/dev/null 2>&1; then + return + fi + CANDIDATES_JSON="$(echo "${CANDIDATES_JSON}" | jq --argjson c "${candidate}" '. + [$c]')" +} + +# --- branch search (one API call for all shortIds) --------------------------- + +echo "fetching open PRs with fix/cli-sentry-triage/ branch prefix..." >&2 +BRANCH_SEARCH_JSON="$( + gh pr list --repo "${FERN_REPO}" --state open \ + --search "fix/cli-sentry-triage/ in:head" \ + --limit 100 \ + --json number,url,headRefName,title,body,state,isDraft,mergedAt,baseRefName 2>/dev/null \ + || echo '[]' +)" + +# --- per shortId -------------------------------------------------------------- + +RESULTS="[]" + +for SHORT_ID in "$@"; do + echo "processing ${SHORT_ID}..." >&2 + CANDIDATES_JSON="[]" + KEBAB_ID="$(normalize_short_id_kebab "${SHORT_ID}")" + + # 1. Ledger lookup (per-shortId file) + LEDGER_FILE="${LEDGER_DIR}/${SHORT_ID}.json" + if [[ -f "${LEDGER_FILE}" ]]; then + LEDGER_ROW="$( + jq '{ + prOrIssue: (.prOrIssue // null), + disposition: (.disposition // null) + }' "${LEDGER_FILE}" + )" + else + LEDGER_ROW="null" + fi + + PR_OR_ISSUE="$(echo "${LEDGER_ROW}" | jq -r '.prOrIssue // empty' 2>/dev/null || true)" + if [[ -n "${PR_OR_ISSUE}" && "${PR_OR_ISSUE}" != "null" ]]; then + if [[ "${PR_OR_ISSUE}" =~ ^https?:// ]]; then + echo " ledger prOrIssue: ${PR_OR_ISSUE}" >&2 + META="$(fetch_pr_metadata "${PR_OR_ISSUE}" "ledger")" + add_candidate "${META}" + fi + fi + + # 2. Triage-branch search (open PRs with fix/cli-sentry-triage/ head only; match shortId in branch, title, or body) + echo " matching triage PRs for shortId ${SHORT_ID}..." >&2 + while IFS= read -r pr_number; do + [[ -z "${pr_number}" ]] && continue + META="$(fetch_pr_metadata "${pr_number}" "triageBranchSearch")" + add_candidate "${META}" + done < <( + echo "${BRANCH_SEARCH_JSON}" \ + | jq -r --arg kebab "${KEBAB_ID}" ' + .[] + | select( + (.headRefName // "" | ascii_downcase | contains($kebab)) + or (.title // "" | test($kebab; "i")) + or (.body // "" | test($kebab; "i")) + ) + | .number + ' 2>/dev/null || true + ) + + # 3. Verification metadata — already included in fetch_pr_metadata (files, state, etc.) + + ENTRY="$( + jq -n \ + --arg shortId "${SHORT_ID}" \ + --argjson ledger "${LEDGER_ROW:-null}" \ + --argjson candidates "${CANDIDATES_JSON}" \ + '{ shortId: $shortId, ledger: $ledger, candidates: $candidates }' + )" + RESULTS="$(echo "${RESULTS}" | jq --argjson entry "${ENTRY}" '. + [$entry]')" +done + +echo "${RESULTS}" | jq '.' diff --git a/CLAUDE.md b/CLAUDE.md index a9a9ed1a3d2d..8bc27af2941d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -393,7 +393,7 @@ After writing the ADR, number it by finding the highest existing `docs/adr/NNNN- ## Troubleshooting -For Sentry `buildwithfern` / `cli` false-positive triage, see [`automation/sentry-triage/AGENT.md`](automation/sentry-triage/AGENT.md), [`automation/sentry-triage/DESIGN_CHOICES.md`](automation/sentry-triage/DESIGN_CHOICES.md), and [`automation/sentry-triage/ledger.json`](automation/sentry-triage/ledger.json). +For Sentry `buildwithfern` / `cli` false-positive triage, see the Devin skill at [`.devin/automation/sentry-triage/SKILL.md`](.devin/automation/sentry-triage/SKILL.md), plus [`.devin/automation/sentry-triage/DESIGN_CHOICES.md`](.devin/automation/sentry-triage/DESIGN_CHOICES.md) and the per-issue ledger files under [`.devin/automation/sentry-triage/ledger/`](.devin/automation/sentry-triage/ledger/). ### Quick Fixes by Issue Type - **Generator failures**: Check `docker ps` → Rebuild image → Check container logs diff --git a/automation/sentry-triage/AGENT.md b/automation/sentry-triage/AGENT.md deleted file mode 100644 index 30e3672fe707..000000000000 --- a/automation/sentry-triage/AGENT.md +++ /dev/null @@ -1,108 +0,0 @@ -# Sentry CLI false-positive triage — agent instructions - -Read **`DESIGN_CHOICES.md`** before proposing code changes; it holds short, accumulated fix patterns. After human review changes the accepted approach, update **`DESIGN_CHOICES.md`** with **1–3 new bullets** (short phrases only) when the lesson is reusable. - -## Scope - -- Sentry org: `buildwithfern` -- Sentry project: `cli` -- Use the repo-baked Sentry CLI: `pnpm exec sentry-cli` (do not rely on a global `sentry` binary). -- Default: unresolved only — `pnpm exec sentry-cli issue list … --query "is:unresolved"` unless the task says to re-validate resolved issues. - -## Run shape - -- **One PR per solution group.** Group Sentry issues by the code change that fixes them: if one change solves multiple `shortId`s, ship those together in one PR; unrelated fixes must be separate PRs. -- **Do not create one catch-all PR for a whole run.** A run may produce multiple PRs, but each PR must correspond to exactly one solution group. -- **Defer** opening PRs until grouping and investigation are far enough along that you are not guessing which `shortId`s share a fix. - -## Fetch (Sentry CLI) - -```bash -pnpm exec sentry-cli issue list buildwithfern/cli \ - --json --fields shortId,title,status,lastSeen \ - --limit 100 --query "is:unresolved" - -pnpm exec sentry-cli issue view buildwithfern/cli/ --json -``` - -Prefer `--json` and `--fields` to keep payloads small. - -## Ledger-first and query-first - -1. Collect `shortId` from list output. -2. Do **not** load all of `automation/sentry-triage/ledger.json` into context as it grows. -3. Targeted lookup with `jq`: - -```bash -jq --argjson ids '["CLI-2W","CLI-2V"]' ' - .issues - | to_entries - | map(select(.key as $id | $ids | index($id))) - | map({ - shortId: .key, - disposition: .value.disposition, - duplicateOf: .value.duplicateOf, - problemSignature: .value.problemSignature, - prOrIssue: .value.prOrIssue - }) -' automation/sentry-triage/ledger.json -``` - -4. **Same `shortId` only:** if this Sentry issue’s `shortId` already exists in the ledger with disposition `shipped`, `ignored`, `duplicate`, or `keep_sentry`, **skip** re-triaging that row (`keep_sentry` means do not suppress—do not reclassify as a false positive). This is **not** “similar title so skip”; it is exact `shortId` match. -5. **Re-process** `pending_review` until set to a terminal disposition. -6. `pnpm exec sentry-cli issue view` in full only for unknown or `pending_review` rows (and tight clustering follow-ups). -7. **Similar past cases (inspiration only):** for a `shortId` **not** in the ledger (or still in play), you may stream `problemSignature` + `shortId` (e.g. `jq -r '.issues | to_entries[] | "\(.key)\t\(.value.problemSignature)"' automation/sentry-triage/ledger.json`) and keyword-search using the **new** issue’s title/exception (MDX, YAML, errno, container, `generators.yml`, etc.) to find **prior fixes to learn from**. **Do not** treat a text match as proof the new issue is already solved: always confirm with current Sentry payload, stack, and code paths. A recurrence or regression still needs a normal investigation and disposition. - -## Ledger shape (`issues`) - -| Field | Required | Meaning | -|-------|----------|---------| -| `title` | yes | Short Sentry title. | -| `problemSignature` | yes | **1–3 short sentences:** symptom + feature surface so **future** triage can grep the ledger for **similar** past cases as **hints** (how we fixed before)—not as proof the new issue is closed. For `duplicate`, copy the canonical row’s `problemSignature` so search still hits this `shortId`. | -| `disposition` | yes | See table below. | -| `rationale` | yes | Why this disposition. | -| `fixSummary` | yes | What changed, or `—` for `duplicate` / `ignored` when not applicable. | -| `prOrIssue` | yes | PR URL, issue link, or note. | -| `lastAnalyzed` | yes | ISO date. | -| `duplicateOf` | if `duplicate` | Canonical ledger `shortId` whose `fixSummary` / PR is the source of truth. | - -## Ledger disposition meanings - -| Value | Meaning | -|-------|---------| -| `shipped` | Fix merged; events should stop after release or issue resolved in Sentry. | -| `ignored` | No code change; rationale recorded. | -| `keep_sentry` | Real bug; stays reportable until a product fix. | -| `duplicate` | Same **Sentry** issue family as another ledger row: `duplicateOf` points at the canonical `shortId` (one fix narrative). **Skip** re-triage for **that** `shortId` only—same as `shipped` for workload. This is unrelated to “looks like” similarity for **new** `shortId`s; those still get full analysis. | -| `pending_review` | Needs decision; resolve on next pass. | - -**Why keep `duplicate`?** Sentry often opens **multiple issues** for one underlying fix. One row stays `shipped` with the full story; the rest become `duplicate` + `duplicateOf` so the next run does not re-investigate **those same ledger shortIds** and the ledger stays small. If you prefer, you can mark every follower `shipped` instead—`duplicate` is only ergonomics. - -## Code and PR rules - -- Follow **`DESIGN_CHOICES.md`** and nearby throw sites in this repo. -- Branch: use `FedeZara/fix/sentry--` for each solution group (for example, `FedeZara/fix/sentry-cli-30-cli-3g-docs-yaml-parse`). Include at most three `shortId`s in the branch name. -- PR title: include the solved `shortId`s in the title. If there are more than three, list only the first three and summarize the family (for example, `fix(cli): classify CLI-30 CLI-3G CLI-Q docs YAML parse errors`). -- Changelog: `packages/cli/cli/changes/unreleased/` when the CLI behavior or reporting changes. -- PR description: explicitly list **each Sentry error solved** by the PR, including its `shortId` (for example, `CLI-2W`) and a short fix note. - -## Phases (single parent agent) - -Optional read-only explore subagents for search only; parent owns edits and the PR. - -1. **Parent** — read this file + `DESIGN_CHOICES.md`; `jq` ledger for current `shortId`s; no PR until grouping is sound. -2. **Fetch** — `pnpm exec sentry-cli issue list` / `pnpm exec sentry-cli issue view` as above. -3. **Ledger filter** — for each **current** `shortId`, if the ledger row exists and disposition is terminal (`shipped`, `ignored`, `duplicate`, `keep_sentry`), skip that issue; keep unknown `shortId`s + `pending_review`. Similar `problemSignature` matches elsewhere do **not** skip an unknown `shortId`. -4. **Group** — by the concrete solution, not just by similar titles. Same code path / same root cause / same patch → one solution-group PR. Different patches → different PRs. -5. **Investigate** — nearest catch/throw; align with `DESIGN_CHOICES.md`. -6. **Fix** — open one PR per solution group; each PR description lists every solved Sentry error with `shortId`; changelog when needed; `keep_sentry` for confirmed real bugs. -7. **Record** — update `ledger.json` in the **same** PR as the code (and `DESIGN_CHOICES.md` when a reusable pattern is confirmed). - -## Record updates after human review - -When the task is to align records with **final** human decisions (no product code unless asked): - -- Load `ledger.json` only (or the specific `issues.` entries you are changing). -- Update ledger fields consistently (including `duplicateOf` if you change which row is canonical, and **`problemSignature`** if humans clarify how this issue should be found next time). -- **Update `DESIGN_CHOICES.md`** when the correction is a reusable pattern: add or replace **short** bullets only—no long narratives. -- Do **not** re-fetch Sentry unless the task requires verification. diff --git a/automation/sentry-triage/README.md b/automation/sentry-triage/README.md deleted file mode 100644 index c2afa466797d..000000000000 --- a/automation/sentry-triage/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# automation/sentry-triage - -Agent triage instructions: **[`AGENT.md`](AGENT.md)**. Fix patterns (short bullets, updated after review): **[`DESIGN_CHOICES.md`](DESIGN_CHOICES.md)**. State: **`ledger.json`**. diff --git a/automation/sentry-triage/ledger.json b/automation/sentry-triage/ledger.json deleted file mode 100644 index ffaa0006eed8..000000000000 --- a/automation/sentry-triage/ledger.json +++ /dev/null @@ -1,1326 +0,0 @@ -{ - "schemaVersion": 3, - "project": "buildwithfern/cli", - "updatedAt": "2026-05-16", - "issues": { - "CLI-2W": { - "title": "59:2: Unexpected character `!` before name", - "disposition": "shipped", - "rationale": "User-authored MDX/acorn parse error surfaced as InternalError; should be ParseError at parseImagePaths call sites.", - "fixSummary": "Wrap parseImagePaths in DocsDefinitionResolver and previewDocs with CliError(ParseError).", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-04-28", - "problemSignature": "Docs or preview path: MDX/acorn parse error (often bad `!` or JSX) while resolving doc image paths; showed as InternalError." - }, - "CLI-2V": { - "title": "MDX / docs image path parse failure", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-2W; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-04-28", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error (often bad `!` or JSX) while resolving doc image paths; showed as InternalError." - }, - "CLI-2X": { - "title": "Filesystem / errno false positive", - "disposition": "shipped", - "rationale": "User/env errno (e.g. ENOENT) misclassified as InternalError.", - "fixSummary": "ErrnoException mapping in resolveErrorCode; USER_ENVIRONMENT_ERRNOS / NETWORK_ERRNOS.", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-1X": { - "title": "Filesystem / errno false positive", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-2X; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-2F": { - "title": "Filesystem / errno false positive", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-2X; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-1S": { - "title": "Filesystem / errno false positive", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-2X; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-2C": { - "title": "Filesystem / errno false positive", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-2X; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-1J": { - "title": "Filesystem / errno false positive", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-2X; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-1V": { - "title": "Filesystem / errno false positive", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-2X; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-1W": { - "title": "Filesystem / errno / EPIPE-style false positive", - "disposition": "shipped", - "rationale": "User/env or pipe closure; EPIPE listener on TtyAwareLogger plus errno mapping.", - "fixSummary": "PR 15283: process.stdout/stderr error listener + errno mapping.", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "problemSignature": "EPIPE or broken pipe when user pipes CLI stdout/stderr to tools like head/jq; errno-style noise reaching error reporter." - }, - "CLI-2E": { - "title": "YAML parse in OpenAPI load", - "disposition": "shipped", - "rationale": "YAMLException from user spec misclassified as InternalError.", - "fixSummary": "Wrap yaml.load in loadOpenAPI; CliError(ParseError) with file/line/column.", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "problemSignature": "Invalid YAML while loading a user OpenAPI spec (yaml.load path); parser exception treated as InternalError." - }, - "CLI-2A": { - "title": "YAML parse in OpenAPI load", - "disposition": "shipped", - "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", - "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-04-21", - "problemSignature": "Invalid YAML while loading a user OpenAPI spec (yaml.load path); parser exception treated as InternalError." - }, - "CLI-24": { - "title": "YAML parse in OpenAPI load", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-2E; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-2E", - "problemSignature": "Invalid YAML while loading a user OpenAPI spec (yaml.load path); parser exception treated as InternalError." - }, - "CLI-1F": { - "title": "YAML parse in OpenAPI load", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-2E; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-2E", - "problemSignature": "Invalid YAML while loading a user OpenAPI spec (yaml.load path); parser exception treated as InternalError." - }, - "CLI-13": { - "title": "swagger2openapi conversion failure", - "disposition": "shipped", - "rationale": "User OpenAPI v2 spec conversion failure should be ParseError.", - "fixSummary": "convertOpenAPIV2ToV3 wraps swagger2openapi as CliError(ParseError).", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "problemSignature": "User OpenAPI v2 document fails swagger2openapi conversion to v3; conversion error treated as InternalError." - }, - "CLI-10": { - "title": "Container aggregate / sdk generate", - "disposition": "shipped", - "rationale": "ContainerError + aggregate InternalError double-report; use TaskAbortSignal.", - "fixSummary": "ContainerError non-reportable; sdk generate uses TaskAbortSignal instead of aggregate CliError.", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." - }, - "CLI-19": { - "title": "Container aggregate / docs publish", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-10; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-10", - "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." - }, - "CLI-2G": { - "title": "Container / task failure reporting", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-10; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-10", - "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." - }, - "CLI-26": { - "title": "Container / task failure reporting", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-10; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-10", - "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." - }, - "CLI-20": { - "title": "Container / task failure reporting", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-10; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-10", - "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." - }, - "CLI-11": { - "title": "Generator not found (generators.yml)", - "disposition": "shipped", - "rationale": "User references unknown generator image; should be ConfigError not InternalError.", - "fixSummary": "Upgrade helper reclassifies to ConfigError.", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "problemSignature": "generators.yml references a generator image or version that is not found in FDR/registry; surfaced as InternalError instead of config error." - }, - "CLI-1H": { - "title": "Organization create validation / network", - "disposition": "shipped", - "rationale": "ValidationError/NetworkError passed explicitly instead of InternalError.", - "fixSummary": "createOrganizationIfDoesNotExist explicit codes.", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "problemSignature": "Organization create flow: validation or network failures bubbled up as InternalError instead of explicit validation/network CLI errors." - }, - "CLI-J": { - "title": "Rerun CLI at version failure", - "disposition": "shipped", - "rationale": "Spurious InternalError on rerun failure; TaskAbortSignal.", - "fixSummary": "rerunFernCliAtVersion uses TaskAbortSignal.", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "problemSignature": "CLI rerun-at-version path failed and reported misleading InternalError instead of controlled task abort." - }, - "CLI-2Z": { - "title": "Schema validation / minified class names", - "disposition": "shipped", - "rationale": "isSchemaValidationError could not match minified constructor names.", - "fixSummary": "Set this.name on JsonError, ParseError, GeneratorError constructors.", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "problemSignature": "Schema validation path: minified build broke constructor-name-based detection so parse/schema errors looked like InternalError." - }, - "CLI-E": { - "title": "Schema validation / minified class names", - "disposition": "duplicate", - "rationale": "Same fix narrative as CLI-2Z; one PR covered both.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "duplicateOf": "CLI-2Z", - "problemSignature": "Schema validation path: minified build broke constructor-name-based detection so parse/schema errors looked like InternalError." - }, - "CLI-2H": { - "title": "Local + GitHub project config resolution", - "disposition": "shipped", - "rationale": "User config / workspace path edge case misclassified as InternalError.", - "fixSummary": "Workspace / project config resolution and path handling (see PR #15283 changelog and follow-up #15484 if refined).", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-04-21", - "problemSignature": "Project load with local vs remote (e.g. GitHub) workspace or config paths: user path/config edge misclassified as InternalError." - }, - "CLI-3Q": { - "title": "ENOENT loading docs page", - "disposition": "duplicate", - "rationale": "Same user/environment errno classification as CLI-2X; current code maps ENOENT to EnvironmentError.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-3A": { - "title": "EINTR interrupted syscall in uv_cwd", - "disposition": "shipped", - "rationale": "EINTR is a clear errno-style environment signal, not a Fern product bug.", - "fixSummary": "Add EINTR to USER_ENVIRONMENT_ERRNOS so resolveErrorCode maps it to EnvironmentError.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Filesystem or syscall errno from user environment (EINTR interrupted syscall) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-H": { - "title": "ExitPromptError after SIGINT", - "disposition": "shipped", - "rationale": "Prompt SIGINT is a user abort; current CliContext prompt wrappers convert ExitPromptError to TaskAbortSignal before reporting.", - "fixSummary": "Handle ExitPromptError at prompt boundaries and abort without Sentry reporting.", - "prOrIssue": "Already handled in current code; ledger corrected in this PR", - "lastAnalyzed": "2026-05-05", - "problemSignature": "Interactive prompt cancellation via SIGINT surfaced as Sentry InternalError instead of a non-reportable user abort." - }, - "CLI-3P": { - "title": "Global theme fetch returned HTTP 403", - "disposition": "shipped", - "rationale": "Global theme lookup failures are auth/config/network conditions, not internal CLI defects.", - "fixSummary": "Pass ConfigError/NetworkError codes from stitchGlobalTheme failAndThrow sites.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Docs global-theme fetch or asset download failure reported through failAndThrow without a non-reportable config/network code." - }, - "CLI-31": { - "title": "Failed to parse version: 0.x", - "disposition": "shipped", - "rationale": "Invalid version strings should be reported with explicit VersionError codes at callers/boundaries, not central name-based classification.", - "fixSummary": "Wrap dependency CLI version parsing with VersionError; existing generator-version callers already wrap invalid versions explicitly.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Invalid generator or CLI version string (e.g. 0.x, 1.x, *) reached Sentry as plain Error instead of VersionError." - }, - "CLI-2P": { - "title": "Failed to parse version: 1.x", - "disposition": "duplicate", - "rationale": "Same invalid-version classification as CLI-31.", - "fixSummary": "—", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-31", - "problemSignature": "Invalid generator or CLI version string (e.g. 0.x, 1.x, *) reached Sentry as plain Error instead of VersionError." - }, - "CLI-2B": { - "title": "Failed to compare versions: Failed to parse version: 0.x", - "disposition": "duplicate", - "rationale": "Same invalid-version classification as CLI-31.", - "fixSummary": "—", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-31", - "problemSignature": "Invalid generator or CLI version string (e.g. 0.x, 1.x, *) reached Sentry as plain Error instead of VersionError." - }, - "CLI-1Z": { - "title": "Failed to parse version: *", - "disposition": "duplicate", - "rationale": "Same invalid-version classification as CLI-31.", - "fixSummary": "—", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-31", - "problemSignature": "Invalid generator or CLI version string (e.g. 0.x, 1.x, *) reached Sentry as plain Error instead of VersionError." - }, - "CLI-14": { - "title": "Generator version incompatible with CLI v4", - "disposition": "duplicate", - "rationale": "Same version/config family as CLI-31; current migration code throws VersionError for this path.", - "fixSummary": "—", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-31", - "problemSignature": "Invalid or incompatible generator version reached Sentry instead of VersionError/config guidance." - }, - "CLI-2M": { - "title": "listen EADDRINUSE :::3001", - "disposition": "duplicate", - "rationale": "EADDRINUSE is already covered by the errno environment mapping.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission, port in use) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-2S": { - "title": "listen EADDRINUSE :::3003", - "disposition": "duplicate", - "rationale": "EADDRINUSE is already covered by the errno environment mapping.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission, port in use) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-Z": { - "title": "ENOSPC no space left on device", - "disposition": "duplicate", - "rationale": "ENOSPC is already covered by the errno environment mapping.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem or syscall errno from user environment (e.g. missing file, permission, disk full) surfaced as InternalError instead of environment-style CLI error." - }, - "CLI-3N": { - "title": "MDX unexpected backslash", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-3M": { - "title": "MDX acorn parse expression", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-3K": { - "title": "MDX acorn parse expression", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-3H": { - "title": "MDX unexpected quote in attribute name", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-37": { - "title": "MDX expected closing Tab tag", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-38": { - "title": "MDX acorn parse expression", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-35": { - "title": "MDX unexpected closing slash", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-33": { - "title": "MDX unexpected asterisk", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-32": { - "title": "MDX expected closing br tag", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-2R": { - "title": "MDX import/export parse failure", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-2Q": { - "title": "MDX acorn parse expression", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-2N": { - "title": "MDX unexpected exclamation", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-29": { - "title": "MDX unexpected exclamation", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-28": { - "title": "MDX unexpected number", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-23": { - "title": "MDX unexpected exclamation", - "disposition": "duplicate", - "rationale": "Same docs MDX/acorn parse family as CLI-2W.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15484", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2W", - "problemSignature": "Docs or preview path: MDX/acorn parse error while resolving user-authored docs content; showed as InternalError." - }, - "CLI-30": { - "title": "YAML multiline key parse failure", - "disposition": "shipped", - "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", - "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." - }, - "CLI-3G": { - "title": "YAML duplicated mapping key", - "disposition": "shipped", - "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", - "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." - }, - "CLI-Q": { - "title": "YAML duplicated mapping key", - "disposition": "shipped", - "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", - "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." - }, - "CLI-1T": { - "title": "YAML duplicated mapping key", - "disposition": "shipped", - "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", - "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." - }, - "CLI-1E": { - "title": "YAML duplicated mapping key", - "disposition": "duplicate", - "rationale": "Same invalid user YAML parse family as CLI-2E.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2E", - "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." - }, - "CLI-G": { - "title": "YAML bad indentation", - "disposition": "shipped", - "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", - "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." - }, - "CLI-P": { - "title": "YAML bad indentation", - "disposition": "shipped", - "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", - "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." - }, - "CLI-N": { - "title": "YAML bad sequence indentation", - "disposition": "shipped", - "rationale": "Docs/workspace YAML parse failure was still able to bubble as InternalError from the docs.yml boundary.", - "fixSummary": "Wrap docs.yml yaml.load in loadRawDocsConfiguration as CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Invalid YAML while loading user-authored config or OpenAPI spec; parser exception treated as InternalError." - }, - "CLI-12": { - "title": "swagger2openapi unresolved reference", - "disposition": "duplicate", - "rationale": "Same OpenAPI v2 conversion failure family as CLI-13.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-13", - "problemSignature": "User OpenAPI v2 document fails swagger2openapi conversion to v3; conversion error treated as InternalError." - }, - "CLI-36": { - "title": "docs navigation schema validation failure", - "disposition": "duplicate", - "rationale": "Same schema validation/minified error-name family as CLI-2Z.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2Z", - "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." - }, - "CLI-F": { - "title": "Unexpected key package-name", - "disposition": "duplicate", - "rationale": "Same schema validation/minified error-name family as CLI-2Z.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2Z", - "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." - }, - "CLI-8": { - "title": "docs.yml navbar-links schema validation failure", - "disposition": "duplicate", - "rationale": "Same schema validation/minified error-name family as CLI-2Z.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2Z", - "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." - }, - "CLI-25": { - "title": "Invalid fern.config.json JSON", - "disposition": "duplicate", - "rationale": "Current loadProjectConfig wraps JSON.parse as ParseError; same non-reportable parse/config class as CLI-2Z.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2Z", - "problemSignature": "Schema or configuration parse path for user-authored fern.config.json looked like InternalError instead of parse/config error." - }, - "CLI-17": { - "title": "Expected list received object", - "disposition": "shipped", - "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", - "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." - }, - "CLI-1G": { - "title": "Expected list received object", - "disposition": "shipped", - "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", - "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." - }, - "CLI-15": { - "title": "Expected list received object", - "disposition": "shipped", - "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", - "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." - }, - "CLI-1D": { - "title": "Expected list received object", - "disposition": "shipped", - "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", - "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." - }, - "CLI-1C": { - "title": "Expected list received object", - "disposition": "shipped", - "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", - "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." - }, - "CLI-1B": { - "title": "Expected list received object", - "disposition": "shipped", - "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", - "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." - }, - "CLI-1A": { - "title": "Expected list received object", - "disposition": "shipped", - "rationale": "IR SDK schema errors lacked stable names, so schema validation failures could still look like InternalError after minification/bundling.", - "fixSummary": "Set stable ParseError/JsonError names in IR SDK schema errors.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Schema or configuration validation path for user-authored docs/config looked like InternalError instead of validation/config error." - }, - "CLI-2Y": { - "title": "No fern directory found with --id", - "disposition": "shipped", - "rationale": "Current delete preview flow passes ValidationError for this user invocation problem.", - "fixSummary": "Existing deleteDocsPreview path uses CliError.Code.ValidationError.", - "prOrIssue": "Already on main", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Docs preview delete --id outside a Fern project failed as InternalError instead of ValidationError guidance." - }, - "CLI-2K": { - "title": "No fern directory found with --id", - "disposition": "duplicate", - "rationale": "Same validation path as CLI-2Y.", - "fixSummary": "—", - "prOrIssue": "Already on main", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-2Y", - "problemSignature": "Docs preview delete --id outside a Fern project failed as InternalError instead of ValidationError guidance." - }, - "CLI-1R": { - "title": "Container execution failed code 1", - "disposition": "duplicate", - "rationale": "Same container/task double-reporting family as CLI-10.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-10", - "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." - }, - "CLI-1P": { - "title": "Container execution failed code 1", - "disposition": "duplicate", - "rationale": "Same container/task double-reporting family as CLI-10.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-10", - "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." - }, - "CLI-1N": { - "title": "Container execution failed code 1", - "disposition": "duplicate", - "rationale": "Same container/task double-reporting family as CLI-10.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-04", - "duplicateOf": "CLI-10", - "problemSignature": "sdk generate or docs publish with Docker/container tasks: task failures double-reported as aggregate InternalError / ContainerError noise." - }, - "CLI-3E": { - "title": "Internal response property conversion error", - "disposition": "keep_sentry", - "rationale": "IR conversion invariant failure in getObjectPropertyFromResolvedType; this looks like a product bug rather than false-positive noise.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-04", - "problemSignature": "IR conversion invariant failure while resolving response object properties; true product defect until converter behavior is fixed." - }, - "CLI-3F": { - "title": "Failed to locate type", - "disposition": "keep_sentry", - "rationale": "Type resolution failed inside IR generation; needs product-level investigation instead of suppression.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-04", - "problemSignature": "IR generation failed to locate a referenced Fern type; possible reference/resolution product bug until validated." - }, - "CLI-3D": { - "title": "Mintlify import navigation is not iterable", - "disposition": "keep_sentry", - "rationale": "Importer TypeError indicates missing validation or compatibility handling in Mintlify import code.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Mintlify docs import TypeError while reading navigation; true importer robustness bug until fixed." - }, - "CLI-3B": { - "title": "Null overlay in applyOverlays", - "disposition": "keep_sentry", - "rationale": "Null overlay TypeError indicates missing validation in workspace overlay handling.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Workspace overlay loading TypeError on null overlay; true validation/product bug until fixed." - }, - "CLI-1Q": { - "title": "formatDocs replace is not a function", - "disposition": "keep_sentry", - "rationale": "formatDocs expected a string and received another shape; needs product validation or conversion fix.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-04", - "problemSignature": "IR docs formatting TypeError because docs value was not a string; true product/data-shape bug until fixed." - }, - "CLI-27": { - "title": "Invalid URL in app preview server", - "disposition": "keep_sentry", - "rationale": "Preview server should validate or normalize the URL source; leave reportable until fixed.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Docs app preview server TypeError Invalid URL; possible product/config boundary bug until fixed." - }, - "CLI-Y": { - "title": "Generic empty Error in development", - "disposition": "ignored", - "rationale": "Only visible event is development environment with empty Error value grouped at telemetry reporter; no product signature to fix or suppress safely.", - "fixSummary": "—", - "prOrIssue": "Ignored after event-level review", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Development-only empty Error grouped at reportError/capture path with no actionable product signature." - }, - "CLI-1Y": { - "title": "Missing translations directory validation", - "disposition": "shipped", - "rationale": "Missing translations directory is user docs configuration and was reported without a non-reportable code.", - "fixSummary": "Pass ValidationError when translation locale directories are missing.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Docs translations configuration references a locale whose translations directory is missing; should be validation/config semantics, not InternalError." - }, - "CLI-3C": { - "title": "Replay resolve failed no-lockfile", - "disposition": "shipped", - "rationale": "Replay resolve no-lockfile is user/worktree state, not an internal CLI defect.", - "fixSummary": "Map fallback replay resolve failures to UserError in both CLI implementations.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Replay resolve failed with no-lockfile or other user/worktree state and was explicitly reported as InternalError." - }, - "CLI-39": { - "title": "Failed to parse GitHub repository", - "disposition": "shipped", - "rationale": "Invalid GitHub repository strings in generators.yml are configuration errors and should be wrapped where generators.yml is converted.", - "fixSummary": "Wrap parseRepository failures in convertGeneratorsConfiguration as CliError(ConfigError); parseRepository still throws a plain Error.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-04", - "problemSignature": "Invalid GitHub repository config string (missing owner/repo or empty path part) surfaced as InternalError instead of ConfigError." - }, - "CLI-1K": { - "title": "GitHub pull request base branch invalid", - "disposition": "shipped", - "rationale": "Remote generation task failures are generator/container task failures, not CLI internal defects.", - "fixSummary": "RemoteTaskHandler maps failed remote tasks to non-reportable ContainerError in current code.", - "prOrIssue": "Already handled in current code; ledger corrected in this PR", - "lastAnalyzed": "2026-05-05", - "problemSignature": "Remote generation task failed because downstream generator/GitHub output rejected user configuration; should be non-reportable task/container failure." - }, - "CLI-3J": { - "title": "Windows temp OpenAPI filepath is not relative", - "disposition": "keep_sentry", - "rationale": "Absolute/Windows path reached a relative-path invariant; needs path normalization or boundary validation fix.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-04", - "problemSignature": "OpenAPI or workspace path conversion received an absolute path where a relative path was required; path boundary bug until fixed." - }, - "CLI-18": { - "title": "Absolute OpenAPI filepath is not relative", - "disposition": "keep_sentry", - "rationale": "Absolute path reached a relative-path invariant; needs path normalization or boundary validation fix.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-04", - "problemSignature": "OpenAPI or workspace path conversion received an absolute path where a relative path was required; path boundary bug until fixed." - }, - "CLI-34": { - "title": "Python package path not found", - "disposition": "shipped", - "rationale": "Library docs generation failed in the external library-docs service; the CLI should not report service job failures as internal defects.", - "fixSummary": "Map failed library-docs generation statuses to NetworkError instead of InternalError.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-05", - "problemSignature": "Library docs generation failed in an external service job; should be non-reportable service/network semantics." - }, - "CLI-2J": { - "title": "Failed to load generator migrations", - "disposition": "shipped", - "rationale": "Migration loading failed because the user's local migration cache/npm environment could not create its cache directory.", - "fixSummary": "Wrap migration package install/load failures as CliError(EnvironmentError) while preserving true CliErrors.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-05", - "problemSignature": "Generator migrations failed to load due local npm/cache/environment error; should be environment semantics." - }, - "CLI-2D": { - "title": "git rm -rf command failed", - "disposition": "shipped", - "rationale": "Generated-output cleanup failed because the user's git worktree/submodule state rejected git rm.", - "fixSummary": "Wrap local generated-output git command failures as CliError(UserError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-05", - "problemSignature": "Git subprocess failure during generated output cleanup reported as InternalError; should be user/worktree semantics." - }, - "CLI-22": { - "title": "Auth0 SSO connection returned HTTP 400", - "disposition": "shipped", - "rationale": "SSO connection resolution 400 is an auth/login boundary failure, not an internal CLI defect.", - "fixSummary": "Map Auth0 SSO resolve HTTP 400 responses to CliError(AuthError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-05", - "problemSignature": "Auth0 login/SSO request returned HTTP 400 and surfaced as AxiosError; should be auth semantics." - }, - "CLI-D": { - "title": "Invalid BAML provider bedrock", - "disposition": "shipped", - "rationale": "Unsupported BAML provider comes from user AI configuration in generators.yml.", - "fixSummary": "Wrap BAML client setup failures at the local-generation AI config boundary as CliError(ConfigError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-05", - "problemSignature": "AI configuration selected an unsupported BAML provider and surfaced as InternalError; should be config semantics." - }, - "CLI-21": { - "title": "User post-generation sed command failed", - "disposition": "shipped", - "rationale": "Placeholder replacement failed in generated files because the local sed command could not process user output bytes.", - "fixSummary": "Wrap auto-version placeholder replacement subprocess failures as CliError(UserError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-05", - "problemSignature": "Auto-version placeholder replacement subprocess failed on generated/user files; should be user semantics." - }, - "CLI-1M": { - "title": "buf generate command failed", - "disposition": "shipped", - "rationale": "buf/protobuf generation is an external local tool boundary; command failures should be user-facing, not internal.", - "fixSummary": "Run buf commands with controlled exit-code handling and map failures to CliError(UserError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-05", - "problemSignature": "buf/protobuf subprocess failed during generation and was reported as InternalError; should be user/tool semantics." - }, - "CLI-K": { - "title": "Package fern-api could not be found", - "disposition": "keep_sentry", - "rationale": "Package manager bootstrap failure needs environment/package-boundary handling; not enough evidence for a safe suppression.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-04", - "problemSignature": "CLI package lookup/bootstrap failed because package could not be found; environment/package boundary needs investigation." - }, - "CLI-C": { - "title": "stream-json parser expected comma", - "disposition": "shipped", - "rationale": "The stream-json parser failure occurs while reading user-provided IR for diff; current code wraps it as ParseError at that boundary.", - "fixSummary": "diff command catches streamObjectFromFile failures and reports CliError(ParseError).", - "prOrIssue": "Already handled in current code; ledger corrected in this PR", - "lastAnalyzed": "2026-05-05", - "problemSignature": "stream-json parser failed on malformed user-provided IR input; should be parse semantics at diff boundary." - }, - "CLI-4N": { - "title": "ENOTEMPTY rmdir generated output", - "disposition": "shipped", - "rationale": "ENOTEMPTY is a user-environment errno (directory not empty during rmdir of generated output); added to USER_ENVIRONMENT_ERRNOS.", - "fixSummary": "Add ENOTEMPTY to USER_ENVIRONMENT_ERRNOS in resolveErrorCode.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "problemSignature": "Filesystem errno ENOTEMPTY during generated-output cleanup surfaced as InternalError instead of EnvironmentError." - }, - "CLI-4R": { - "title": "ENOTEMPTY rmdir generated Java SDK", - "disposition": "duplicate", - "rationale": "Same ENOTEMPTY errno classification as CLI-4N.", - "fixSummary": "—", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-4N", - "problemSignature": "Filesystem errno ENOTEMPTY during generated-output cleanup surfaced as InternalError instead of EnvironmentError." - }, - "CLI-4S": { - "title": "ENOTEMPTY rmdir generated Java SDK", - "disposition": "duplicate", - "rationale": "Same ENOTEMPTY errno classification as CLI-4N.", - "fixSummary": "—", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-4N", - "problemSignature": "Filesystem errno ENOTEMPTY during generated-output cleanup surfaced as InternalError instead of EnvironmentError." - }, - "CLI-3S": { - "title": "ENOMEM open user spec file", - "disposition": "shipped", - "rationale": "ENOMEM is a user-environment errno (out of memory opening file); added to USER_ENVIRONMENT_ERRNOS.", - "fixSummary": "Add ENOMEM to USER_ENVIRONMENT_ERRNOS in resolveErrorCode.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "problemSignature": "Filesystem errno ENOMEM while opening user spec file surfaced as InternalError instead of EnvironmentError." - }, - "CLI-43": { - "title": "TypeError: fetch failed (docs resolver)", - "disposition": "shipped", - "rationale": "Undici TypeError: fetch failed escapes as unhandled error; wrapped fetch calls at boundary with try/catch → NetworkError in uploadDynamicIRs, SourceUploader, and uploadDynamicIRForSdkGeneration.", - "fixSummary": "Wrap bare fetch() calls in uploadDynamicIRs, SourceUploader.uploadSource, and uploadDynamicIRForSdkGeneration with try/catch that classifies network failures as NetworkError at the call site.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "problemSignature": "Undici TypeError fetch failed during docs resolution surfaced as InternalError instead of NetworkError." - }, - "CLI-4H": { - "title": "TypeError: fetch failed (source upload)", - "disposition": "duplicate", - "rationale": "Same boundary-level fetch wrapping as CLI-43; SourceUploader.uploadSource now catches TypeError and classifies as NetworkError.", - "fixSummary": "—", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-43", - "problemSignature": "Undici TypeError fetch failed during source upload surfaced as InternalError instead of NetworkError." - }, - "CLI-3R": { - "title": "EPERM mkdir unhandled rejection", - "disposition": "keep_sentry", - "rationale": "EPERM from PosthogManager mkdir escapes normal error handling as unhandled rejection; reaches Sentry via onUnhandledRejectionIntegration. Needs boundary-level wrapping at the PosthogManager call site.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until boundary-level fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "Unhandled rejection with EPERM errno from background telemetry bypassed resolveErrorCode and reached Sentry as raw exception." - }, - "CLI-4Z": { - "title": "ENOSPC file watcher unhandled rejection", - "disposition": "keep_sentry", - "rationale": "ENOSPC from file watcher escapes normal error handling as unhandled rejection; reaches Sentry via onUnhandledRejectionIntegration. Needs boundary-level wrapping at the file watcher call site.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until boundary-level fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "Unhandled rejection with ENOSPC errno from file watcher bypassed resolveErrorCode and reached Sentry as raw exception." - }, - "CLI-4M": { - "title": "EPIPE write uncaught exception", - "disposition": "keep_sentry", - "rationale": "EPIPE from stdout/stderr write escapes normal error handling as uncaught exception; reaches Sentry via onUncaughtExceptionIntegration. Needs boundary-level wrapping at the stream write call site.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until boundary-level fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "Uncaught exception with EPIPE errno from stream write bypassed resolveErrorCode and reached Sentry as raw exception." - }, - "CLI-3Y": { - "title": "spawn xdg-open ENOENT uncaught exception", - "disposition": "keep_sentry", - "rationale": "ENOENT from spawn xdg-open escapes normal error handling as uncaught exception; reaches Sentry via onUncaughtExceptionIntegration. Needs boundary-level wrapping at the spawn call site.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until boundary-level fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "Uncaught exception with ENOENT errno from spawn xdg-open bypassed resolveErrorCode and reached Sentry as raw exception." - }, - "CLI-3W": { - "title": "ENOENT reading OpenAPI spec", - "disposition": "duplicate", - "rationale": "ENOENT is already mapped to EnvironmentError by the errno classification shipped in CLI-2X.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem errno ENOENT from user environment surfaced as InternalError instead of EnvironmentError." - }, - "CLI-3X": { - "title": "ENOENT reading OpenAPI spec", - "disposition": "duplicate", - "rationale": "ENOENT is already mapped to EnvironmentError by the errno classification shipped in CLI-2X.", - "fixSummary": "—", - "prOrIssue": "https://github.com/fern-api/fern/pull/15283", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-2X", - "problemSignature": "Filesystem errno ENOENT from user environment surfaced as InternalError instead of EnvironmentError." - }, - "CLI-40": { - "title": "Received 429 Too Many Requests", - "disposition": "shipped", - "rationale": "HTTP 429 rate-limit from remote generation service. retryWithRateLimit now throws CliError(NetworkError) on exhaustion, and generateOne passes error object through failWithoutThrowing so resolveErrorCode propagates the code.", - "fixSummary": "Change retryWithRateLimit exhaustion code from InternalError to NetworkError; pass error object in generateOne catch to failWithoutThrowing.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "problemSignature": "TooManyRequestsError from retryWithRateLimit reaching Sentry because failWithoutThrowing(message) lost the error object and code." - }, - "CLI-41": { - "title": "Docs preview server failed to start: Server process exited with code 1", - "disposition": "shipped", - "rationale": "Next.js subprocess failing to start is an environment issue (port conflict, missing deps, etc.), not a Fern bug. Reclassified from InternalError to EnvironmentError.", - "fixSummary": "Change failAndThrow code from InternalError to EnvironmentError in runAppPreviewServer.ts.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "problemSignature": "startNextJsServer failure classified as InternalError instead of EnvironmentError." - }, - "CLI-4B": { - "title": "Failed to load generator migrations for fernapi/fern-typescript-sdk", - "disposition": "shipped", - "rationale": "npm install failure (EACCES on rename) in migration cache. loadMigrationModule already wraps non-CliError exceptions as CliError(EnvironmentError) at the boundary.", - "fixSummary": "Already fixed: loadMigrationModule catch block wraps with EnvironmentError.", - "prOrIssue": "This PR (ledger only)", - "lastAnalyzed": "2026-05-16", - "problemSignature": "npm cache rename EACCES inside ensureMigrationsInstalled, caught and wrapped as EnvironmentError by loadMigrationModule." - }, - "CLI-4F": { - "title": "Global theme \"dark\" not found for org \"asi\"", - "disposition": "shipped", - "rationale": "User references a global theme that does not exist. stitchGlobalTheme already passes ConfigError to failAndThrow for 404 and NOT_FOUND responses.", - "fixSummary": "Already fixed: failAndThrow passes { code: ConfigError } for theme-not-found.", - "prOrIssue": "This PR (ledger only)", - "lastAnalyzed": "2026-05-16", - "problemSignature": "Global theme 404/NOT_FOUND classified as ConfigError by stitchGlobalTheme." - }, - "CLI-4Q": { - "title": "Failed to fetch global theme \"nvidia\": HTTP 403", - "disposition": "shipped", - "rationale": "HTTP 403 from FDR when fetching a global theme the org lacks access to. stitchGlobalTheme already passes ConfigError to failAndThrow for non-ok HTTP responses.", - "fixSummary": "Already fixed: failAndThrow passes { code: ConfigError } for non-ok HTTP status.", - "prOrIssue": "This PR (ledger only)", - "lastAnalyzed": "2026-05-16", - "problemSignature": "Global theme HTTP 403 classified as ConfigError by stitchGlobalTheme." - }, - "CLI-44": { - "title": "MDX expected closing Tip tag", - "disposition": "shipped", - "rationale": "MDX parse error from user-authored docs content. replaceImagePathsAndUrls in DocsDefinitionResolver and previewDocs now wrapped with try/catch that converts to CliError(ParseError).", - "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." - }, - "CLI-45": { - "title": "MDX expected closing div tag", - "disposition": "shipped", - "rationale": "Same MDX parse boundary family as CLI-44.", - "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-44", - "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." - }, - "CLI-47": { - "title": "MDX expected closing img tag", - "disposition": "shipped", - "rationale": "Same MDX parse boundary family as CLI-44.", - "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-44", - "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." - }, - "CLI-48": { - "title": "MDX unexpected character before name", - "disposition": "shipped", - "rationale": "Same MDX parse boundary family as CLI-44.", - "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-44", - "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." - }, - "CLI-4A": { - "title": "MDX unexpected quote in attribute name", - "disposition": "shipped", - "rationale": "Same MDX parse boundary family as CLI-44.", - "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-44", - "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." - }, - "CLI-4E": { - "title": "MDX unexpected quote in attribute name", - "disposition": "shipped", - "rationale": "Same MDX parse boundary family as CLI-44.", - "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-44", - "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." - }, - "CLI-4K": { - "title": "MDX acorn parse expression", - "disposition": "shipped", - "rationale": "Same MDX parse boundary family as CLI-44.", - "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-44", - "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." - }, - "CLI-3V": { - "title": "MDX unexpected character before name", - "disposition": "shipped", - "rationale": "Same MDX parse boundary family as CLI-44.", - "fixSummary": "Wrap replaceImagePathsAndUrls calls in DocsDefinitionResolver.resolve and getPreviewDocsDefinition with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "duplicateOf": "CLI-44", - "problemSignature": "MDX parse error surfaced as InternalError from unwrapped replaceImagePathsAndUrls call sites." - }, - "CLI-42": { - "title": "Generic Error (minified, no actionable signature)", - "disposition": "ignored", - "rationale": "Single event with generic Error and no useful stack trace or context after minification; no actionable product signature to fix or suppress safely.", - "fixSummary": "—", - "prOrIssue": "Ignored after event-level review", - "lastAnalyzed": "2026-05-16", - "problemSignature": "Minified generic Error with no actionable stack trace or context." - }, - "CLI-46": { - "title": "Filepath is not relative (Linux absolute path)", - "disposition": "keep_sentry", - "rationale": "Absolute path reached a relative-path invariant; same family as CLI-3J and CLI-18.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "OpenAPI or workspace path conversion received an absolute path where a relative path was required; path boundary bug." - }, - "CLI-4C": { - "title": "Endpoint not found in readme config", - "disposition": "keep_sentry", - "rationale": "convertReadmeConfig could not match an endpoint; either user readme config is wrong or the endpoint matcher has a product bug.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "IR generation readme config endpoint matcher failed to locate a matching endpoint; needs product investigation." - }, - "CLI-4G": { - "title": "Endpoint not found in readme config", - "disposition": "keep_sentry", - "rationale": "Same readme config endpoint resolution family as CLI-4C.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "IR generation readme config endpoint matcher failed to locate a matching endpoint; needs product investigation." - }, - "CLI-4J": { - "title": "Unsupported OpenAPI version 3.2.0", - "disposition": "keep_sentry", - "rationale": "Redocly threw for unsupported OpenAPI 3.2.0; the CLI should either support it or classify as ParseError at the OpenAPI loading boundary. Needs product decision.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "Redocly rejected user OpenAPI spec version 3.2.0 as unsupported; needs product decision on classification or support." - }, - "CLI-4P": { - "title": "Undefined server name in IR-to-FDR conversion", - "disposition": "keep_sentry", - "rationale": "ir-to-fdr-converter encountered an undefined server name; this is a product/conversion bug in server URL mapping.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "IR-to-FDR converter encountered undefined server name at endpoint; server URL mapping product bug." - }, - "CLI-4T": { - "title": "Failed to find PronunciationItem", - "disposition": "keep_sentry", - "rationale": "FDR SDK example generation failed to resolve a type; this is a product/SDK bug in generateEndpointExampleCall.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "FDR SDK example generation failed to locate a referenced type during endpoint example call generation; true product bug." - }, - "CLI-4V": { - "title": "Cannot find declaration of type in IR generator", - "disposition": "keep_sentry", - "rationale": "IR generator TypeResolver could not find a declared type; this is a product/resolution bug.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "IR generation TypeResolver failed to locate a declared type; possible reference/resolution product bug." - }, - "CLI-4W": { - "title": "Failed to find NorthErrorType", - "disposition": "keep_sentry", - "rationale": "FDR SDK example generation failed to resolve a type; same product bug family as CLI-4T.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "FDR SDK example generation failed to locate a referenced type during endpoint example call generation; true product bug." - }, - "CLI-4X": { - "title": "Failed to find Error type", - "disposition": "keep_sentry", - "rationale": "FDR SDK example generation failed to resolve a type; same product bug family as CLI-4T.", - "fixSummary": "—", - "prOrIssue": "Keep in Sentry until product fix", - "lastAnalyzed": "2026-05-16", - "problemSignature": "FDR SDK example generation failed to locate a referenced type during endpoint example call generation; true product bug." - } - }, - "CLI-49": { - "title": "YAMLException: can not read a block mapping entry; a multiline key may not be an implicit key", - "disposition": "shipped", - "rationale": "YAML parse error from user-authored version config file (RSA key embedded in YAML). yaml.load in getVersionedNavigationConfiguration now wrapped with try/catch that converts to CliError(ParseError).", - "fixSummary": "Wrap yaml.load calls in getVersionedNavigationConfiguration and getNavigationConfiguration with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "problemSignature": "YAMLException surfaced as InternalError from unwrapped yaml.load in getVersionedNavigationConfiguration." - }, - "CLI-4Y": { - "title": "YAMLException: end of the stream or a document separator is expected", - "disposition": "shipped", - "rationale": "YAML parse error from user-authored product config file. yaml.load in getNavigationConfiguration now wrapped with try/catch that converts to CliError(ParseError).", - "fixSummary": "Wrap yaml.load calls in getVersionedNavigationConfiguration and getNavigationConfiguration with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "problemSignature": "YAMLException surfaced as InternalError from unwrapped yaml.load in getNavigationConfiguration." - }, - "CLI-3T": { - "title": "YAMLException: bad indentation of a sequence entry", - "disposition": "shipped", - "rationale": "YAML parse error from user-authored docs config. js-yaml throws YAMLException which bubbles through unwrapped yaml.load call sites. Now caught at getVersionedNavigationConfiguration and getNavigationConfiguration boundaries.", - "fixSummary": "Wrap yaml.load calls in getVersionedNavigationConfiguration and getNavigationConfiguration with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "problemSignature": "YAMLException from js-yaml surfaced as InternalError through unwrapped yaml.load call sites." - }, - "CLI-3Z": { - "title": "YAMLException: bad indentation of a mapping entry", - "disposition": "shipped", - "rationale": "YAML parse error from user-authored docs config. js-yaml throws YAMLException which bubbles through unwrapped yaml.load call sites. Now caught at getVersionedNavigationConfiguration and getNavigationConfiguration boundaries.", - "fixSummary": "Wrap yaml.load calls in getVersionedNavigationConfiguration and getNavigationConfiguration with try/catch → CliError(ParseError).", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "problemSignature": "YAMLException from js-yaml surfaced as InternalError through unwrapped yaml.load call sites." - }, - "CLI-4D": { - "title": "YAMLException: bad indentation of a sequence entry", - "disposition": "shipped", - "rationale": "YAML parse error from user-authored docs.yml. Already caught at loadRawDocsConfiguration boundary (loadDocsWorkspace.ts:69-79). This PR adds coverage for the remaining unwrapped yaml.load call sites in version and product file loading.", - "fixSummary": "loadRawDocsConfiguration already wraps yaml.load with try/catch → CliError(ParseError). This PR extends the pattern to version/product file loading.", - "prOrIssue": "This PR", - "lastAnalyzed": "2026-05-16", - "problemSignature": "YAMLException surfaced as InternalError from loadRawDocsConfiguration (already fixed in that call site)." - } -} diff --git a/fern-yml.schema.json b/fern-yml.schema.json new file mode 100644 index 000000000000..9f988793bb0c --- /dev/null +++ b/fern-yml.schema.json @@ -0,0 +1,6448 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "edition": { + "type": "string" + }, + "org": { + "type": "string" + }, + "ai": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "enum": [ + "openai", + "anthropic", + "bedrock" + ] + }, + "model": { + "type": "string" + } + }, + "required": [ + "provider", + "model" + ], + "additionalProperties": false + }, + "cli": { + "type": "object", + "properties": { + "version": { + "type": "string" + } + }, + "additionalProperties": false + }, + "docs": { + "type": "object", + "properties": { + "instances": { + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "custom-domain": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "edit-this-page": { + "type": "object", + "properties": { + "github": { + "type": "object", + "properties": { + "host": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "repo": { + "type": "string" + }, + "branch": { + "type": "string" + } + }, + "required": [ + "owner", + "repo" + ], + "additionalProperties": false + }, + "launch": { + "type": "string", + "enum": [ + "github", + "dashboard" + ] + } + }, + "additionalProperties": false + }, + "audiences": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "multi-source": { + "type": "boolean" + } + }, + "required": [ + "url" + ], + "additionalProperties": false + } + }, + "title": { + "type": "string" + }, + "libraries": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "input": { + "anyOf": [ + { + "type": "object", + "properties": { + "git": { + "type": "string" + }, + "subpath": { + "type": "string" + } + }, + "required": [ + "git" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + }, + "output": { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + }, + "lang": { + "type": "string", + "enum": [ + "python", + "cpp" + ] + } + }, + "required": [ + "input", + "output", + "lang" + ], + "additionalProperties": false + } + }, + "analytics": { + "type": "object", + "properties": { + "segment": { + "type": "object", + "properties": { + "write-key": { + "type": "string" + } + }, + "required": [ + "write-key" + ], + "additionalProperties": false + }, + "fullstory": { + "type": "object", + "properties": { + "org-id": { + "type": "string" + } + }, + "required": [ + "org-id" + ], + "additionalProperties": false + }, + "intercom": { + "type": "object", + "properties": { + "app-id": { + "type": "string" + }, + "api-base": { + "type": "string" + } + }, + "required": [ + "app-id" + ], + "additionalProperties": false + }, + "posthog": { + "type": "object", + "properties": { + "api-key": { + "type": "string" + }, + "endpoint": { + "type": "string" + } + }, + "required": [ + "api-key" + ], + "additionalProperties": false + }, + "gtm": { + "type": "object", + "properties": { + "container-id": { + "type": "string" + } + }, + "required": [ + "container-id" + ], + "additionalProperties": false + }, + "ga4": { + "type": "object", + "properties": { + "measurement-id": { + "type": "string" + } + }, + "required": [ + "measurement-id" + ], + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "announcement": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "tabs": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "display-name": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "skip-slug": { + "type": "boolean" + }, + "hidden": { + "type": "boolean" + }, + "href": { + "type": "string" + }, + "target": { + "type": "string", + "enum": [ + "_blank", + "_self", + "_parent", + "_top" + ] + }, + "changelog": { + "type": "string" + } + }, + "required": [ + "display-name" + ], + "additionalProperties": false + } + }, + "versions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "display-name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "availability": { + "type": "string", + "enum": [ + "deprecated", + "ga", + "stable", + "beta" + ] + }, + "audiences": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "hidden": { + "type": "boolean" + }, + "announcement": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + } + }, + "required": [ + "display-name", + "path" + ], + "additionalProperties": false + } + }, + "products": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "display-name": { + "type": "string" + }, + "subtitle": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "image": { + "type": "string" + }, + "versions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "display-name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "availability": { + "type": "string", + "enum": [ + "deprecated", + "ga", + "stable", + "beta" + ] + }, + "audiences": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "hidden": { + "type": "boolean" + }, + "announcement": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + } + }, + "required": [ + "display-name", + "path" + ], + "additionalProperties": false + } + }, + "audiences": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "path": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "announcement": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + } + }, + "required": [ + "display-name", + "path" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "display-name": { + "type": "string" + }, + "subtitle": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "image": { + "type": "string" + }, + "versions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "display-name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "availability": { + "type": "string", + "enum": [ + "deprecated", + "ga", + "stable", + "beta" + ] + }, + "audiences": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "hidden": { + "type": "boolean" + }, + "announcement": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + } + }, + "required": [ + "display-name", + "path" + ], + "additionalProperties": false + } + }, + "audiences": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "href": { + "type": "string" + }, + "target": { + "type": "string", + "enum": [ + "_blank", + "_self", + "_parent", + "_top" + ] + } + }, + "required": [ + "display-name", + "href" + ], + "additionalProperties": false + } + ] + } + }, + "landing-page": { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "page": { + "type": "string" + }, + "path": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "noindex": { + "type": "boolean" + }, + "availability": { + "type": "string", + "enum": [ + "stable", + "generally-available", + "in-development", + "pre-release", + "deprecated", + "beta" + ] + } + }, + "required": [ + "page", + "path" + ], + "additionalProperties": false + }, + "navigation": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/__schema0" + } + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "tab": { + "type": "string" + }, + "layout": { + "type": "array", + "items": { + "$ref": "#/$defs/__schema0" + } + } + }, + "required": [ + "tab" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "tab": { + "type": "string" + }, + "variants": { + "type": "array", + "items": { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "title": { + "type": "string" + }, + "subtitle": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "layout": { + "type": "array", + "items": { + "$ref": "#/$defs/__schema0" + } + }, + "slug": { + "type": "string" + }, + "skip-slug": { + "type": "boolean" + }, + "hidden": { + "type": "boolean" + }, + "default": { + "type": "boolean" + } + }, + "required": [ + "title", + "layout" + ], + "additionalProperties": false + } + } + }, + "required": [ + "tab", + "variants" + ], + "additionalProperties": false + } + ] + } + } + ] + }, + "navbar-links": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "href": { + "type": "string" + }, + "target": { + "type": "string", + "enum": [ + "_blank", + "_self", + "_parent", + "_top" + ] + }, + "url": { + "type": "string" + }, + "text": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "rightIcon": { + "type": "string" + }, + "rounded": { + "type": "boolean" + }, + "type": { + "type": "string", + "const": "filled" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "href": { + "type": "string" + }, + "target": { + "type": "string", + "enum": [ + "_blank", + "_self", + "_parent", + "_top" + ] + }, + "url": { + "type": "string" + }, + "text": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "rightIcon": { + "type": "string" + }, + "rounded": { + "type": "boolean" + }, + "type": { + "type": "string", + "const": "outlined" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "href": { + "type": "string" + }, + "target": { + "type": "string", + "enum": [ + "_blank", + "_self", + "_parent", + "_top" + ] + }, + "url": { + "type": "string" + }, + "text": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "rightIcon": { + "type": "string" + }, + "rounded": { + "type": "boolean" + }, + "type": { + "type": "string", + "const": "minimal" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "github" + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "url": { + "type": "string" + }, + "target": { + "type": "string", + "enum": [ + "_blank", + "_self", + "_parent", + "_top" + ] + } + }, + "required": [ + "url" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "links": { + "type": "array", + "items": { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "href": { + "type": "string" + }, + "target": { + "type": "string", + "enum": [ + "_blank", + "_self", + "_parent", + "_top" + ] + }, + "url": { + "type": "string" + }, + "text": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "rightIcon": { + "type": "string" + }, + "rounded": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "text": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "rightIcon": { + "type": "string" + }, + "rounded": { + "type": "boolean" + }, + "type": { + "type": "string", + "const": "dropdown" + } + }, + "required": [ + "links", + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "href": { + "type": "string" + }, + "target": { + "type": "string", + "enum": [ + "_blank", + "_self", + "_parent", + "_top" + ] + }, + "url": { + "type": "string" + }, + "text": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "rightIcon": { + "type": "string" + }, + "rounded": { + "type": "boolean" + }, + "type": { + "type": "string", + "const": "primary" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "href": { + "type": "string" + }, + "target": { + "type": "string", + "enum": [ + "_blank", + "_self", + "_parent", + "_top" + ] + }, + "url": { + "type": "string" + }, + "text": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "rightIcon": { + "type": "string" + }, + "rounded": { + "type": "boolean" + }, + "type": { + "type": "string", + "const": "secondary" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] + } + }, + "footer-links": { + "type": "object", + "properties": { + "github": { + "type": "string" + }, + "twitter": { + "type": "string" + }, + "x": { + "type": "string" + }, + "linkedin": { + "type": "string" + }, + "youtube": { + "type": "string" + }, + "instagram": { + "type": "string" + }, + "facebook": { + "type": "string" + }, + "discord": { + "type": "string" + }, + "slack": { + "type": "string" + }, + "hackernews": { + "type": "string" + }, + "medium": { + "type": "string" + }, + "website": { + "type": "string" + } + }, + "additionalProperties": false + }, + "page-actions": { + "type": "object", + "properties": { + "default": { + "type": "string", + "enum": [ + "copy-page", + "view-as-markdown", + "ask-ai", + "chatgpt", + "claude", + "cursor", + "vscode" + ] + }, + "options": { + "type": "object", + "properties": { + "copy-page": { + "type": "boolean" + }, + "view-as-markdown": { + "type": "boolean" + }, + "ask-ai": { + "type": "boolean" + }, + "chatgpt": { + "type": "boolean" + }, + "claude": { + "type": "boolean" + }, + "cursor": { + "type": "boolean" + }, + "vscode": { + "type": "boolean" + }, + "custom": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "subtitle": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "default": { + "type": "boolean" + } + }, + "required": [ + "title", + "url" + ], + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "experimental": { + "type": "object", + "properties": { + "mdx-components": { + "type": "array", + "items": { + "type": "string" + } + }, + "disable-stream-toggle": { + "type": "boolean" + }, + "openapi-parser-v2": { + "type": "boolean" + }, + "openapi-parser-v3": { + "type": "boolean" + }, + "dynamic-snippets": { + "type": "boolean" + }, + "ai-examples": { + "type": "boolean" + }, + "ai-example-style-instructions": { + "type": "string" + }, + "exclude-apis": { + "type": "boolean" + }, + "basepath-aware": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "default-language": { + "type": "string", + "enum": [ + "typescript", + "javascript", + "python", + "java", + "go", + "ruby", + "csharp", + "php", + "swift", + "rust", + "nodets", + "nodejs", + "dotnet", + "curl", + "jvm", + "ts", + "js" + ] + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "translations": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "pattern": "^[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*$" + }, + { + "type": "object", + "properties": { + "lang": { + "type": "string", + "pattern": "^[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*$" + }, + "default": { + "type": "boolean" + } + }, + "required": [ + "lang" + ], + "additionalProperties": false + } + ] + } + }, + "ai-chat": { + "type": "object", + "properties": { + "model": { + "type": "string", + "enum": [ + "claude-3.7", + "claude-4", + "command-a" + ] + }, + "system-prompt": { + "type": "string" + }, + "location": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "docs", + "slack", + "discord" + ] + } + }, + "datasources": { + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "url" + ], + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "ai-search": { + "type": "object", + "properties": { + "model": { + "type": "string", + "enum": [ + "claude-3.7", + "claude-4", + "command-a" + ] + }, + "system-prompt": { + "type": "string" + }, + "location": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "docs", + "slack", + "discord" + ] + } + }, + "datasources": { + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "url" + ], + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "ai-examples": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "style": { + "type": "string" + } + }, + "additionalProperties": false + }, + "agents": { + "type": "object", + "properties": { + "page-directive": { + "type": "string" + }, + "page-description-source": { + "type": "string", + "enum": [ + "description", + "subtitle" + ] + }, + "llms-txt": { + "type": "string" + }, + "llms-full-txt": { + "type": "string" + } + }, + "additionalProperties": false + }, + "metadata": { + "type": "object", + "properties": { + "og:site_name": { + "type": "string" + }, + "og:title": { + "type": "string" + }, + "og:description": { + "type": "string" + }, + "og:url": { + "type": "string" + }, + "og:image": { + "type": "string" + }, + "og:image:width": { + "type": "number" + }, + "og:image:height": { + "type": "number" + }, + "og:locale": { + "type": "string" + }, + "og:logo": { + "type": "string" + }, + "twitter:title": { + "type": "string" + }, + "twitter:description": { + "type": "string" + }, + "twitter:handle": { + "type": "string" + }, + "twitter:image": { + "type": "string" + }, + "twitter:site": { + "type": "string" + }, + "twitter:url": { + "type": "string" + }, + "twitter:card": { + "type": "string", + "enum": [ + "summary", + "summary_large_image", + "app", + "player" + ] + }, + "og:dynamic": { + "type": "boolean" + }, + "og:dynamic:background-image": { + "type": "string" + }, + "og:dynamic:text-color": { + "type": "string" + }, + "og:dynamic:background-color": { + "type": "string" + }, + "og:dynamic:logo-color": { + "type": "string", + "enum": [ + "dark", + "light" + ] + }, + "og:dynamic:show-logo": { + "type": "boolean" + }, + "og:dynamic:show-section": { + "type": "boolean" + }, + "og:dynamic:show-description": { + "type": "boolean" + }, + "og:dynamic:show-url": { + "type": "boolean" + }, + "og:dynamic:show-gradient": { + "type": "boolean" + }, + "canonical-host": { + "type": "string" + } + }, + "additionalProperties": false + }, + "redirects": { + "type": "array", + "items": { + "type": "object", + "properties": { + "source": { + "type": "string" + }, + "destination": { + "type": "string" + }, + "permanent": { + "type": "boolean" + } + }, + "required": [ + "source", + "destination" + ], + "additionalProperties": false + } + }, + "check": { + "type": "object", + "properties": { + "rules": { + "type": "object", + "properties": { + "example-validation": { + "type": "string", + "enum": [ + "warn", + "error" + ] + }, + "broken-links": { + "type": "string", + "enum": [ + "warn", + "error" + ] + }, + "no-non-component-refs": { + "type": "string", + "enum": [ + "warn", + "error" + ] + }, + "valid-local-references": { + "type": "string", + "enum": [ + "warn", + "error" + ] + }, + "no-circular-redirects": { + "type": "string", + "enum": [ + "warn", + "error" + ] + }, + "valid-docs-endpoints": { + "type": "string", + "enum": [ + "warn", + "error" + ] + }, + "missing-redirects": { + "type": "string", + "enum": [ + "warn", + "error" + ] + }, + "valid-changelog-slug": { + "type": "string", + "enum": [ + "warn", + "error" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "logo": { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + }, + "height": { + "type": "number" + }, + "href": { + "type": "string" + }, + "right-text": { + "type": "string" + } + }, + "additionalProperties": false + }, + "favicon": { + "type": "string" + }, + "background-image": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "colors": { + "type": "object", + "properties": { + "accent-primary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accentPrimary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "background": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "border": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "sidebar-background": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "header-background": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "card-background": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-1": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-2": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-3": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-4": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-5": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-6": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-7": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-8": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-9": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-10": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-11": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "accent-12": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + }, + "typography": { + "type": "object", + "properties": { + "headingsFont": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "weight": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + ] + }, + "style": { + "type": "string", + "enum": [ + "normal", + "italic" + ] + }, + "paths": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "weight": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + ] + }, + "style": { + "type": "string", + "enum": [ + "normal", + "italic" + ] + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + } + }, + "display": { + "type": "string", + "enum": [ + "auto", + "block", + "swap", + "fallback", + "optional" + ] + }, + "fallback": { + "type": "array", + "items": { + "type": "string" + } + }, + "font-variation-settings": { + "type": "string" + } + }, + "additionalProperties": false + }, + "bodyFont": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "weight": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + ] + }, + "style": { + "type": "string", + "enum": [ + "normal", + "italic" + ] + }, + "paths": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "weight": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + ] + }, + "style": { + "type": "string", + "enum": [ + "normal", + "italic" + ] + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + } + }, + "display": { + "type": "string", + "enum": [ + "auto", + "block", + "swap", + "fallback", + "optional" + ] + }, + "fallback": { + "type": "array", + "items": { + "type": "string" + } + }, + "font-variation-settings": { + "type": "string" + } + }, + "additionalProperties": false + }, + "codeFont": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "weight": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + ] + }, + "style": { + "type": "string", + "enum": [ + "normal", + "italic" + ] + }, + "paths": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "weight": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + ] + }, + "style": { + "type": "string", + "enum": [ + "normal", + "italic" + ] + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + } + }, + "display": { + "type": "string", + "enum": [ + "auto", + "block", + "swap", + "fallback", + "optional" + ] + }, + "fallback": { + "type": "array", + "items": { + "type": "string" + } + }, + "font-variation-settings": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "layout": { + "type": "object", + "properties": { + "page-width": { + "type": "string" + }, + "content-width": { + "type": "string" + }, + "sidebar-width": { + "type": "string" + }, + "header-height": { + "type": "string" + }, + "searchbar-placement": { + "type": "string", + "enum": [ + "header", + "header-tabs", + "sidebar" + ] + }, + "tabs-placement": { + "type": "string", + "enum": [ + "header", + "sidebar" + ] + }, + "switcher-placement": { + "type": "string", + "enum": [ + "header", + "sidebar" + ] + }, + "content-alignment": { + "type": "string", + "enum": [ + "center", + "left" + ] + }, + "header-position": { + "type": "string", + "enum": [ + "fixed", + "static" + ] + }, + "disable-header": { + "type": "boolean" + }, + "hide-nav-links": { + "type": "boolean" + }, + "hide-feedback": { + "type": "boolean" + }, + "mobile-toc": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "settings": { + "type": "object", + "properties": { + "search-text": { + "type": "string" + }, + "disable-search": { + "type": "boolean" + }, + "dark-mode-code": { + "type": "boolean" + }, + "default-search-filters": { + "type": "boolean" + }, + "http-snippets": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "curl", + "csharp", + "go", + "java", + "javascript", + "php", + "python", + "ruby", + "swift", + "typescript" + ] + } + } + ] + }, + "hide-404-page": { + "type": "boolean" + }, + "use-javascript-as-typescript": { + "type": "boolean" + }, + "disable-explorer-proxy": { + "type": "boolean" + }, + "disable-analytics": { + "type": "boolean" + }, + "language": { + "type": "string", + "pattern": "^[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*$" + }, + "folder-title-source": { + "type": "string", + "enum": [ + "frontmatter", + "filename" + ] + }, + "substitute-env-vars": { + "type": "boolean" + }, + "websocket-oneof-display": { + "type": "string", + "enum": [ + "flat", + "grouped" + ] + } + }, + "additionalProperties": false + }, + "theme": { + "type": "object", + "properties": { + "sidebar": { + "type": "string", + "enum": [ + "default", + "minimal" + ] + }, + "body": { + "type": "string", + "enum": [ + "default", + "canvas" + ] + }, + "tabs": { + "type": "string", + "enum": [ + "default", + "bubble" + ] + }, + "page-actions": { + "type": "string", + "enum": [ + "default", + "toolbar" + ] + }, + "footer-nav": { + "type": "string", + "enum": [ + "default", + "minimal" + ] + }, + "language-switcher": { + "type": "string", + "enum": [ + "default", + "minimal" + ] + }, + "product-switcher": { + "type": "string", + "enum": [ + "default", + "toggle", + "tabs" + ] + } + }, + "additionalProperties": false + }, + "global-theme": { + "type": "string" + }, + "integrations": { + "type": "object", + "properties": { + "intercom": { + "type": "string" + }, + "context7": { + "type": "string" + } + }, + "additionalProperties": false + }, + "css": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "js": { + "anyOf": [ + { + "anyOf": [ + { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "strategy": { + "type": "string", + "enum": [ + "beforeInteractive", + "afterInteractive", + "lazyOnload" + ] + } + }, + "required": [ + "url" + ], + "additionalProperties": false + }, + { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "strategy": { + "type": "string", + "enum": [ + "beforeInteractive", + "afterInteractive", + "lazyOnload" + ] + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + } + ] + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "strategy": { + "type": "string", + "enum": [ + "beforeInteractive", + "afterInteractive", + "lazyOnload" + ] + } + }, + "required": [ + "url" + ], + "additionalProperties": false + }, + { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "strategy": { + "type": "string", + "enum": [ + "beforeInteractive", + "afterInteractive", + "lazyOnload" + ] + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + } + ] + } + } + ] + }, + "header": { + "type": "string" + }, + "footer": { + "type": "string" + } + }, + "required": [ + "instances" + ], + "additionalProperties": false + }, + "sdks": { + "type": "object", + "properties": { + "autorelease": { + "type": "boolean" + }, + "defaultGroup": { + "type": "string" + }, + "readme": { + "type": "object", + "properties": { + "bannerLink": { + "type": "string" + }, + "introduction": { + "type": "string" + }, + "apiReferenceLink": { + "type": "string" + }, + "apiName": { + "type": "string" + }, + "disabledSections": { + "type": "array", + "items": { + "type": "string" + } + }, + "defaultEndpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "method": { + "type": "string" + }, + "path": { + "type": "string" + }, + "stream": { + "type": "boolean" + } + }, + "required": [ + "method", + "path" + ], + "additionalProperties": false + } + ] + }, + "features": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "method": { + "type": "string" + }, + "path": { + "type": "string" + }, + "stream": { + "type": "boolean" + } + }, + "required": [ + "method", + "path" + ], + "additionalProperties": false + } + ] + } + } + }, + "customSections": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "language": { + "type": "string", + "enum": [ + "csharp", + "go", + "java", + "php", + "python", + "ruby", + "rust", + "swift", + "typescript" + ] + }, + "content": { + "type": "string" + } + }, + "required": [ + "title", + "content" + ], + "additionalProperties": false + } + }, + "exampleStyle": { + "type": "string", + "enum": [ + "minimal", + "comprehensive" + ] + } + }, + "additionalProperties": false + }, + "targets": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "api": { + "type": "string" + }, + "lang": { + "type": "string", + "enum": [ + "csharp", + "go", + "java", + "php", + "python", + "ruby", + "rust", + "swift", + "typescript" + ] + }, + "image": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "registry": { + "type": "string" + } + }, + "required": [ + "name", + "registry" + ], + "additionalProperties": false + } + ] + }, + "version": { + "type": "string" + }, + "config": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "publish": { + "type": "object", + "properties": { + "npm": { + "type": "object", + "properties": { + "packageName": { + "type": "string" + }, + "url": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "required": [ + "packageName" + ], + "additionalProperties": false + }, + "pypi": { + "type": "object", + "properties": { + "packageName": { + "type": "string" + }, + "url": { + "type": "string" + }, + "token": { + "type": "string" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "metadata": { + "type": "object", + "properties": { + "keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "documentationLink": { + "type": "string" + }, + "homepageLink": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "required": [ + "packageName" + ], + "additionalProperties": false + }, + "maven": { + "type": "object", + "properties": { + "coordinate": { + "type": "string" + }, + "url": { + "type": "string" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "signature": { + "type": "object", + "properties": { + "keyId": { + "type": "string" + }, + "password": { + "type": "string" + }, + "secretKey": { + "type": "string" + } + }, + "required": [ + "keyId", + "password", + "secretKey" + ], + "additionalProperties": false + } + }, + "required": [ + "coordinate" + ], + "additionalProperties": false + }, + "nuget": { + "type": "object", + "properties": { + "packageName": { + "type": "string" + }, + "url": { + "type": "string" + }, + "apiKey": { + "type": "string" + } + }, + "required": [ + "packageName" + ], + "additionalProperties": false + }, + "rubygems": { + "type": "object", + "properties": { + "packageName": { + "type": "string" + }, + "url": { + "type": "string" + }, + "apiKey": { + "type": "string" + } + }, + "required": [ + "packageName" + ], + "additionalProperties": false + }, + "crates": { + "type": "object", + "properties": { + "packageName": { + "type": "string" + }, + "url": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "required": [ + "packageName" + ], + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "output": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "git": { + "anyOf": [ + { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "token": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "pr", + "push", + "release" + ] + }, + "branch": { + "type": "string" + }, + "license": { + "type": "string" + } + }, + "required": [ + "uri", + "token" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "repository": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "pr", + "release", + "push" + ] + }, + "branch": { + "type": "string" + }, + "license": { + "type": "string" + }, + "reviewers": { + "type": "object", + "properties": { + "teams": { + "type": "array", + "items": { + "type": "string" + } + }, + "users": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "required": [ + "repository" + ], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + ] + }, + "group": { + "type": "array", + "items": { + "type": "string" + } + }, + "metadata": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "authors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "required": [ + "name", + "email" + ], + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "readme": { + "type": "object", + "properties": { + "bannerLink": { + "type": "string" + }, + "introduction": { + "type": "string" + }, + "apiReferenceLink": { + "type": "string" + }, + "apiName": { + "type": "string" + }, + "disabledSections": { + "type": "array", + "items": { + "type": "string" + } + }, + "defaultEndpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "method": { + "type": "string" + }, + "path": { + "type": "string" + }, + "stream": { + "type": "boolean" + } + }, + "required": [ + "method", + "path" + ], + "additionalProperties": false + } + ] + }, + "features": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "method": { + "type": "string" + }, + "path": { + "type": "string" + }, + "stream": { + "type": "boolean" + } + }, + "required": [ + "method", + "path" + ], + "additionalProperties": false + } + ] + } + } + }, + "customSections": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "language": { + "type": "string", + "enum": [ + "csharp", + "go", + "java", + "php", + "python", + "ruby", + "rust", + "swift", + "typescript" + ] + }, + "content": { + "type": "string" + } + }, + "required": [ + "title", + "content" + ], + "additionalProperties": false + } + }, + "exampleStyle": { + "type": "string", + "enum": [ + "minimal", + "comprehensive" + ] + } + }, + "additionalProperties": false + } + }, + "required": [ + "output" + ], + "additionalProperties": false + } + } + }, + "required": [ + "targets" + ], + "additionalProperties": false + }, + "api": { + "type": "object", + "properties": { + "specs": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "openapi": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "overrides": { + "anyOf": [ + { + "type": "string" + }, + { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "overlays": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "settings": { + "type": "object", + "properties": { + "respectNullableSchemas": { + "type": "boolean" + }, + "wrapReferencesToNullableInOptional": { + "type": "boolean" + }, + "coerceOptionalSchemasToNullable": { + "type": "boolean" + }, + "titleAsSchemaName": { + "type": "boolean" + }, + "coerceEnumsToLiterals": { + "type": "boolean" + }, + "optionalAdditionalProperties": { + "type": "boolean" + }, + "idiomaticRequestNames": { + "type": "boolean" + }, + "groupEnvironmentsByHost": { + "type": "boolean" + }, + "multiServerStrategy": { + "type": "string", + "enum": [ + "environmentPerServer", + "urlsPerEnvironment" + ] + }, + "removeDiscriminantsFromSchemas": { + "type": "string", + "enum": [ + "always", + "never" + ] + }, + "pathParameterOrder": { + "type": "string", + "enum": [ + "urlOrder", + "specOrder" + ] + }, + "coerceConstsTo": { + "type": "string", + "enum": [ + "literals", + "enums", + "enums-coerceable-to-literals" + ] + }, + "onlyIncludeReferencedSchemas": { + "type": "boolean" + }, + "inlinePathParameters": { + "type": "boolean" + }, + "preferUndiscriminatedUnionsWithLiterals": { + "type": "boolean" + }, + "objectQueryParameters": { + "type": "boolean" + }, + "respectReadonlySchemas": { + "type": "boolean" + }, + "respectForwardCompatibleEnums": { + "type": "boolean" + }, + "useBytesForBinaryResponse": { + "type": "boolean" + }, + "defaultFormParameterEncoding": { + "type": "string", + "enum": [ + "form", + "json" + ] + }, + "filter": { + "type": "object", + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "exampleGeneration": { + "type": "object", + "properties": { + "request": { + "type": "object", + "properties": { + "maxDepth": { + "type": "number" + } + }, + "additionalProperties": false + }, + "response": { + "type": "object", + "properties": { + "maxDepth": { + "type": "number" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "additionalPropertiesDefaultsTo": { + "type": "boolean" + }, + "typeDatesAsStrings": { + "type": "boolean" + }, + "preserveSingleSchemaOneof": { + "type": "boolean" + }, + "inlineAllOfSchemas": { + "type": "boolean" + }, + "resolveAliases": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "except": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "groupMultiApiEnvironments": { + "type": "boolean" + }, + "defaultIntegerFormat": { + "type": "string", + "enum": [ + "int32", + "int64", + "uint32", + "uint64" + ] + }, + "inferDiscriminatedUnionBaseProperties": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "required": [ + "openapi" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "asyncapi": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "overrides": { + "anyOf": [ + { + "type": "string" + }, + { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "namespace": { + "type": "string" + }, + "settings": { + "type": "object", + "properties": { + "respectNullableSchemas": { + "type": "boolean" + }, + "wrapReferencesToNullableInOptional": { + "type": "boolean" + }, + "coerceOptionalSchemasToNullable": { + "type": "boolean" + }, + "titleAsSchemaName": { + "type": "boolean" + }, + "coerceEnumsToLiterals": { + "type": "boolean" + }, + "optionalAdditionalProperties": { + "type": "boolean" + }, + "idiomaticRequestNames": { + "type": "boolean" + }, + "groupEnvironmentsByHost": { + "type": "boolean" + }, + "multiServerStrategy": { + "type": "string", + "enum": [ + "environmentPerServer", + "urlsPerEnvironment" + ] + }, + "removeDiscriminantsFromSchemas": { + "type": "string", + "enum": [ + "always", + "never" + ] + }, + "pathParameterOrder": { + "type": "string", + "enum": [ + "urlOrder", + "specOrder" + ] + }, + "coerceConstsTo": { + "type": "string", + "enum": [ + "literals", + "enums", + "enums-coerceable-to-literals" + ] + }, + "messageNaming": { + "type": "string", + "enum": [ + "v1", + "v2" + ] + } + }, + "additionalProperties": false + } + }, + "required": [ + "asyncapi" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "proto": { + "type": "object", + "properties": { + "root": { + "type": "string" + }, + "target": { + "type": "string" + }, + "overrides": { + "anyOf": [ + { + "type": "string" + }, + { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "localGeneration": { + "type": "boolean" + }, + "fromOpenapi": { + "type": "boolean" + }, + "dependencies": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "root" + ], + "additionalProperties": false + }, + "settings": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "required": [ + "proto" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "fern": { + "type": "string" + }, + "settings": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "required": [ + "fern" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "conjure": { + "type": "string" + }, + "settings": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "required": [ + "conjure" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "openrpc": { + "type": "string" + }, + "overrides": { + "anyOf": [ + { + "type": "string" + }, + { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "settings": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "required": [ + "openrpc" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "graphql": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "overrides": { + "anyOf": [ + { + "type": "string" + }, + { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "graphql" + ], + "additionalProperties": false + } + ] + } + }, + "auth": { + "type": "string" + }, + "defaultUrl": { + "type": "string" + }, + "defaultEnvironment": { + "type": "string" + }, + "environments": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "docs": { + "type": "string" + } + }, + "required": [ + "url" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "urls": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "docs": { + "type": "string" + } + }, + "required": [ + "urls" + ], + "additionalProperties": false + } + ] + } + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "env": { + "type": "string" + }, + "docs": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + }, + "authSchemes": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": [ + "specs" + ], + "additionalProperties": false + }, + "apis": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "object", + "properties": { + "specs": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "openapi": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "overrides": { + "anyOf": [ + { + "type": "string" + }, + { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "overlays": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "settings": { + "type": "object", + "properties": { + "respectNullableSchemas": { + "type": "boolean" + }, + "wrapReferencesToNullableInOptional": { + "type": "boolean" + }, + "coerceOptionalSchemasToNullable": { + "type": "boolean" + }, + "titleAsSchemaName": { + "type": "boolean" + }, + "coerceEnumsToLiterals": { + "type": "boolean" + }, + "optionalAdditionalProperties": { + "type": "boolean" + }, + "idiomaticRequestNames": { + "type": "boolean" + }, + "groupEnvironmentsByHost": { + "type": "boolean" + }, + "multiServerStrategy": { + "type": "string", + "enum": [ + "environmentPerServer", + "urlsPerEnvironment" + ] + }, + "removeDiscriminantsFromSchemas": { + "type": "string", + "enum": [ + "always", + "never" + ] + }, + "pathParameterOrder": { + "type": "string", + "enum": [ + "urlOrder", + "specOrder" + ] + }, + "coerceConstsTo": { + "type": "string", + "enum": [ + "literals", + "enums", + "enums-coerceable-to-literals" + ] + }, + "onlyIncludeReferencedSchemas": { + "type": "boolean" + }, + "inlinePathParameters": { + "type": "boolean" + }, + "preferUndiscriminatedUnionsWithLiterals": { + "type": "boolean" + }, + "objectQueryParameters": { + "type": "boolean" + }, + "respectReadonlySchemas": { + "type": "boolean" + }, + "respectForwardCompatibleEnums": { + "type": "boolean" + }, + "useBytesForBinaryResponse": { + "type": "boolean" + }, + "defaultFormParameterEncoding": { + "type": "string", + "enum": [ + "form", + "json" + ] + }, + "filter": { + "type": "object", + "properties": { + "endpoints": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "exampleGeneration": { + "type": "object", + "properties": { + "request": { + "type": "object", + "properties": { + "maxDepth": { + "type": "number" + } + }, + "additionalProperties": false + }, + "response": { + "type": "object", + "properties": { + "maxDepth": { + "type": "number" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "additionalPropertiesDefaultsTo": { + "type": "boolean" + }, + "typeDatesAsStrings": { + "type": "boolean" + }, + "preserveSingleSchemaOneof": { + "type": "boolean" + }, + "inlineAllOfSchemas": { + "type": "boolean" + }, + "resolveAliases": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "except": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "groupMultiApiEnvironments": { + "type": "boolean" + }, + "defaultIntegerFormat": { + "type": "string", + "enum": [ + "int32", + "int64", + "uint32", + "uint64" + ] + }, + "inferDiscriminatedUnionBaseProperties": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "required": [ + "openapi" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "asyncapi": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "overrides": { + "anyOf": [ + { + "type": "string" + }, + { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "namespace": { + "type": "string" + }, + "settings": { + "type": "object", + "properties": { + "respectNullableSchemas": { + "type": "boolean" + }, + "wrapReferencesToNullableInOptional": { + "type": "boolean" + }, + "coerceOptionalSchemasToNullable": { + "type": "boolean" + }, + "titleAsSchemaName": { + "type": "boolean" + }, + "coerceEnumsToLiterals": { + "type": "boolean" + }, + "optionalAdditionalProperties": { + "type": "boolean" + }, + "idiomaticRequestNames": { + "type": "boolean" + }, + "groupEnvironmentsByHost": { + "type": "boolean" + }, + "multiServerStrategy": { + "type": "string", + "enum": [ + "environmentPerServer", + "urlsPerEnvironment" + ] + }, + "removeDiscriminantsFromSchemas": { + "type": "string", + "enum": [ + "always", + "never" + ] + }, + "pathParameterOrder": { + "type": "string", + "enum": [ + "urlOrder", + "specOrder" + ] + }, + "coerceConstsTo": { + "type": "string", + "enum": [ + "literals", + "enums", + "enums-coerceable-to-literals" + ] + }, + "messageNaming": { + "type": "string", + "enum": [ + "v1", + "v2" + ] + } + }, + "additionalProperties": false + } + }, + "required": [ + "asyncapi" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "proto": { + "type": "object", + "properties": { + "root": { + "type": "string" + }, + "target": { + "type": "string" + }, + "overrides": { + "anyOf": [ + { + "type": "string" + }, + { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "localGeneration": { + "type": "boolean" + }, + "fromOpenapi": { + "type": "boolean" + }, + "dependencies": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "root" + ], + "additionalProperties": false + }, + "settings": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "required": [ + "proto" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "fern": { + "type": "string" + }, + "settings": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "required": [ + "fern" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "conjure": { + "type": "string" + }, + "settings": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "required": [ + "conjure" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "openrpc": { + "type": "string" + }, + "overrides": { + "anyOf": [ + { + "type": "string" + }, + { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "settings": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "required": [ + "openrpc" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "graphql": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "overrides": { + "anyOf": [ + { + "type": "string" + }, + { + "minItems": 1, + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "graphql" + ], + "additionalProperties": false + } + ] + } + }, + "auth": { + "type": "string" + }, + "defaultUrl": { + "type": "string" + }, + "defaultEnvironment": { + "type": "string" + }, + "environments": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "docs": { + "type": "string" + } + }, + "required": [ + "url" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "urls": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "docs": { + "type": "string" + } + }, + "required": [ + "urls" + ], + "additionalProperties": false + } + ] + } + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "env": { + "type": "string" + }, + "docs": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + }, + "authSchemes": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": [ + "specs" + ], + "additionalProperties": false + } + } + }, + "required": [ + "org" + ], + "additionalProperties": false, + "$defs": { + "__schema0": { + "anyOf": [ + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "page": { + "type": "string" + }, + "path": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "noindex": { + "type": "boolean" + }, + "availability": { + "type": "string", + "enum": [ + "stable", + "generally-available", + "in-development", + "pre-release", + "deprecated", + "beta" + ] + } + }, + "required": [ + "page", + "path" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "section": { + "type": "string" + }, + "path": { + "type": "string" + }, + "contents": { + "type": "array", + "items": { + "$ref": "#/$defs/__schema0" + } + }, + "collapsed": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "enum": [ + "open-by-default" + ] + } + ] + }, + "collapsible": { + "type": "boolean" + }, + "collapsed-by-default": { + "type": "boolean" + }, + "slug": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "skip-slug": { + "type": "boolean" + }, + "availability": { + "type": "string", + "enum": [ + "stable", + "generally-available", + "in-development", + "pre-release", + "deprecated", + "beta" + ] + } + }, + "required": [ + "section", + "contents" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "api": { + "type": "string" + }, + "api-name": { + "type": "string" + }, + "openrpc": { + "type": "string" + }, + "audiences": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "display-errors": { + "type": "boolean" + }, + "tag-description-pages": { + "type": "boolean" + }, + "snippets": { + "type": "object", + "properties": { + "python": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "version", + "package" + ], + "additionalProperties": false + } + ] + }, + "typescript": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "version", + "package" + ], + "additionalProperties": false + } + ] + }, + "go": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "version", + "package" + ], + "additionalProperties": false + } + ] + }, + "java": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "version", + "package" + ], + "additionalProperties": false + } + ] + }, + "ruby": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "version", + "package" + ], + "additionalProperties": false + } + ] + }, + "csharp": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "version", + "package" + ], + "additionalProperties": false + } + ] + }, + "php": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "version", + "package" + ], + "additionalProperties": false + } + ] + }, + "swift": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "version", + "package" + ], + "additionalProperties": false + } + ] + }, + "rust": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "package": { + "type": "string" + } + }, + "required": [ + "version", + "package" + ], + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + }, + "postman": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "layout": { + "type": "array", + "items": { + "$ref": "#/$defs/__schema1" + } + }, + "collapsed": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "enum": [ + "open-by-default" + ] + } + ] + }, + "icon": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "availability": { + "type": "string", + "enum": [ + "stable", + "generally-available", + "in-development", + "pre-release", + "deprecated", + "beta" + ] + }, + "skip-slug": { + "type": "boolean" + }, + "alphabetized": { + "type": "boolean" + }, + "flattened": { + "type": "boolean" + }, + "paginated": { + "type": "boolean" + }, + "playground": { + "type": "object", + "properties": { + "hidden": { + "type": "boolean" + }, + "environments": { + "type": "array", + "items": { + "type": "string" + } + }, + "button": { + "type": "object", + "properties": { + "href": { + "type": "string" + } + }, + "additionalProperties": false + }, + "oauth": { + "type": "boolean" + }, + "limit-websocket-messages-per-connection": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "additionalProperties": false + } + }, + "required": [ + "api" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "library": { + "type": "string" + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + } + }, + "required": [ + "library" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "link": { + "type": "string" + }, + "href": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "target": { + "type": "string", + "enum": [ + "_blank", + "_self", + "_parent", + "_top" + ] + } + }, + "required": [ + "link", + "href" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "changelog": { + "type": "string" + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "hidden": { + "type": "boolean" + } + }, + "required": [ + "changelog" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "folder": { + "type": "string" + }, + "title": { + "type": "string" + }, + "title-source": { + "type": "string", + "enum": [ + "frontmatter", + "filename" + ] + }, + "slug": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "skip-slug": { + "type": "boolean" + }, + "collapsed": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "enum": [ + "open-by-default" + ] + } + ] + }, + "collapsible": { + "type": "boolean" + }, + "collapsed-by-default": { + "type": "boolean" + }, + "availability": { + "type": "string", + "enum": [ + "stable", + "generally-available", + "in-development", + "pre-release", + "deprecated", + "beta" + ] + } + }, + "required": [ + "folder" + ], + "additionalProperties": false + } + ] + }, + "__schema1": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/__schema1" + } + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "title": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "contents": { + "type": "array", + "items": { + "$ref": "#/$defs/__schema1" + } + }, + "availability": { + "type": "string", + "enum": [ + "stable", + "generally-available", + "in-development", + "pre-release", + "deprecated", + "beta" + ] + }, + "slug": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "skip-slug": { + "type": "boolean" + }, + "playground": { + "type": "object", + "properties": { + "hidden": { + "type": "boolean" + }, + "environments": { + "type": "array", + "items": { + "type": "string" + } + }, + "button": { + "type": "object", + "properties": { + "href": { + "type": "string" + } + }, + "additionalProperties": false + }, + "oauth": { + "type": "boolean" + }, + "limit-websocket-messages-per-connection": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "section": { + "type": "string" + }, + "referenced-packages": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "contents": { + "type": "array", + "items": { + "$ref": "#/$defs/__schema1" + } + }, + "slug": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "skip-slug": { + "type": "boolean" + }, + "collapsed": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "enum": [ + "open-by-default" + ] + } + ] + }, + "collapsible": { + "type": "boolean" + }, + "collapsed-by-default": { + "type": "boolean" + }, + "availability": { + "type": "string", + "enum": [ + "stable", + "generally-available", + "in-development", + "pre-release", + "deprecated", + "beta" + ] + }, + "playground": { + "type": "object", + "properties": { + "hidden": { + "type": "boolean" + }, + "environments": { + "type": "array", + "items": { + "type": "string" + } + }, + "button": { + "type": "object", + "properties": { + "href": { + "type": "string" + } + }, + "additionalProperties": false + }, + "oauth": { + "type": "boolean" + }, + "limit-websocket-messages-per-connection": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "additionalProperties": false + } + }, + "required": [ + "section" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "endpoint": { + "type": "string" + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "availability": { + "type": "string", + "enum": [ + "stable", + "generally-available", + "in-development", + "pre-release", + "deprecated", + "beta" + ] + }, + "playground": { + "type": "object", + "properties": { + "hidden": { + "type": "boolean" + }, + "environments": { + "type": "array", + "items": { + "type": "string" + } + }, + "button": { + "type": "object", + "properties": { + "href": { + "type": "string" + } + }, + "additionalProperties": false + }, + "oauth": { + "type": "boolean" + }, + "limit-websocket-messages-per-connection": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "additionalProperties": false + } + }, + "required": [ + "endpoint" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "operation": { + "type": "string" + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "availability": { + "type": "string", + "enum": [ + "stable", + "generally-available", + "in-development", + "pre-release", + "deprecated", + "beta" + ] + } + }, + "required": [ + "operation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "viewers": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "orphaned": { + "type": "boolean" + }, + "feature-flag": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "flag": { + "type": "string" + }, + "fallback-value": {}, + "match": {} + }, + "required": [ + "flag" + ], + "additionalProperties": false + } + } + ] + }, + "page": { + "type": "string" + }, + "path": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "noindex": { + "type": "boolean" + }, + "availability": { + "type": "string", + "enum": [ + "stable", + "generally-available", + "in-development", + "pre-release", + "deprecated", + "beta" + ] + } + }, + "required": [ + "page", + "path" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "link": { + "type": "string" + }, + "href": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "target": { + "type": "string", + "enum": [ + "_blank", + "_self", + "_parent", + "_top" + ] + } + }, + "required": [ + "link", + "href" + ], + "additionalProperties": false + } + ] + } + } +} diff --git a/generators/cli/changes/0.1.0/add-cli-generator.yml b/generators/cli/changes/0.1.0/add-cli-generator.yml index 383d1331bbe4..82687b8ab1d7 100644 --- a/generators/cli/changes/0.1.0/add-cli-generator.yml +++ b/generators/cli/changes/0.1.0/add-cli-generator.yml @@ -1,3 +1,6 @@ - summary: | Add CLI generator infrastructure and registration. type: feat +- summary: | + Add raw API spec file mounting and embedding for runtime CLI construction. + type: feat diff --git a/generators/cli/changes/0.2.0/add-spec-preprocessing.yml b/generators/cli/changes/0.2.0/add-spec-preprocessing.yml new file mode 100644 index 000000000000..c054c7c4082c --- /dev/null +++ b/generators/cli/changes/0.2.0/add-spec-preprocessing.yml @@ -0,0 +1,5 @@ +- summary: | + Add API spec pre-processing infrastructure: bundle external $refs, + merge overrides, apply overlays, and output compact JSON for embedding + in generated CLI. + type: feat diff --git a/generators/cli/src/__test__/analyzeSpecs.test.ts b/generators/cli/src/__test__/analyzeSpecs.test.ts new file mode 100644 index 000000000000..311dbb06d94f --- /dev/null +++ b/generators/cli/src/__test__/analyzeSpecs.test.ts @@ -0,0 +1,212 @@ +import { mkdir, writeFile } from "fs/promises"; +import { tmpdir } from "os"; +import path from "path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { analyzeSpecs, formatSpecAnalysis } from "../analyzeSpecs.js"; +import { SPECS_MANIFEST_FILENAME } from "../copySpecs.js"; + +describe("analyzeSpecs", () => { + let tmpDir: string; + + beforeEach(async () => { + tmpDir = path.join(tmpdir(), `analyze-specs-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); + await mkdir(tmpDir, { recursive: true }); + }); + + afterEach(async () => { + const { rm } = await import("fs/promises"); + await rm(tmpDir, { recursive: true, force: true }); + }); + + it("returns empty analysis when no manifest exists", async () => { + const result = await analyzeSpecs(tmpDir); + expect(result.totalSpecs).toBe(0); + expect(result.totalEndpoints).toBe(0); + expect(result.totalSchemas).toBe(0); + expect(result.specs).toHaveLength(0); + }); + + it("returns empty analysis when manifest has no specs", async () => { + await writeFile(path.join(tmpDir, SPECS_MANIFEST_FILENAME), JSON.stringify({ specs: [] })); + const result = await analyzeSpecs(tmpDir); + expect(result.totalSpecs).toBe(0); + }); + + it("analyzes a single OpenAPI spec", async () => { + const spec = { + openapi: "3.0.0", + info: { title: "Pet Store", version: "1.2.3" }, + paths: { + "/pets": { get: { summary: "List pets" }, post: { summary: "Create pet" } }, + "/pets/{id}": { get: { summary: "Get pet" }, delete: { summary: "Delete pet" } } + }, + components: { + schemas: { + Pet: { type: "object" }, + Error: { type: "object" } + } + } + }; + const specPath = path.join(tmpDir, "spec-0.json"); + await writeFile(specPath, JSON.stringify(spec)); + await writeFile( + path.join(tmpDir, SPECS_MANIFEST_FILENAME), + JSON.stringify({ specs: [{ type: "openapi", specPath }] }) + ); + + const result = await analyzeSpecs(tmpDir); + expect(result.totalSpecs).toBe(1); + expect(result.totalEndpoints).toBe(4); + expect(result.totalSchemas).toBe(2); + expect(result.specs[0]?.title).toBe("Pet Store"); + expect(result.specs[0]?.version).toBe("1.2.3"); + }); + + it("analyzes an AsyncAPI spec with channels", async () => { + const spec = { + asyncapi: "2.6.0", + info: { title: "Events API", version: "0.1.0" }, + channels: { + "user/signup": { subscribe: {} }, + "order/created": { publish: {} } + } + }; + const specPath = path.join(tmpDir, "spec-0.json"); + await writeFile(specPath, JSON.stringify(spec)); + await writeFile( + path.join(tmpDir, SPECS_MANIFEST_FILENAME), + JSON.stringify({ specs: [{ type: "asyncapi", specPath }] }) + ); + + const result = await analyzeSpecs(tmpDir); + expect(result.totalSpecs).toBe(1); + expect(result.totalEndpoints).toBe(2); + expect(result.specs[0]?.title).toBe("Events API"); + }); + + it("analyzes an OpenRPC spec with methods", async () => { + const spec = { + openrpc: "1.0.0", + info: { title: "JSON-RPC Service", version: "2.0.0" }, + methods: [{ name: "eth_getBalance" }, { name: "eth_call" }, { name: "eth_sendTransaction" }] + }; + const specPath = path.join(tmpDir, "spec-0.json"); + await writeFile(specPath, JSON.stringify(spec)); + await writeFile( + path.join(tmpDir, SPECS_MANIFEST_FILENAME), + JSON.stringify({ specs: [{ type: "openrpc", specPath }] }) + ); + + const result = await analyzeSpecs(tmpDir); + expect(result.totalSpecs).toBe(1); + expect(result.totalEndpoints).toBe(3); + expect(result.specs[0]?.title).toBe("JSON-RPC Service"); + }); + + it("returns zero counts for protobuf specs", async () => { + const specPath = path.join(tmpDir, "proto"); + await mkdir(specPath, { recursive: true }); + await writeFile( + path.join(tmpDir, SPECS_MANIFEST_FILENAME), + JSON.stringify({ specs: [{ type: "protobuf", specPath }] }) + ); + + const result = await analyzeSpecs(tmpDir); + expect(result.totalSpecs).toBe(1); + expect(result.totalEndpoints).toBe(0); + expect(result.totalSchemas).toBe(0); + }); + + it("handles multiple specs and sums totals", async () => { + const spec1 = { + openapi: "3.0.0", + info: { title: "API 1", version: "1.0.0" }, + paths: { "/a": { get: {} } }, + components: { schemas: { A: { type: "object" } } } + }; + const spec2 = { + openapi: "3.0.0", + info: { title: "API 2", version: "2.0.0" }, + paths: { "/b": { post: {} }, "/c": { put: {} } }, + components: { schemas: { B: { type: "object" }, C: { type: "object" } } } + }; + const specPath1 = path.join(tmpDir, "spec-0.json"); + const specPath2 = path.join(tmpDir, "spec-1.json"); + await writeFile(specPath1, JSON.stringify(spec1)); + await writeFile(specPath2, JSON.stringify(spec2)); + await writeFile( + path.join(tmpDir, SPECS_MANIFEST_FILENAME), + JSON.stringify({ + specs: [ + { type: "openapi", specPath: specPath1 }, + { type: "openapi", specPath: specPath2 } + ] + }) + ); + + const result = await analyzeSpecs(tmpDir); + expect(result.totalSpecs).toBe(2); + expect(result.totalEndpoints).toBe(3); + expect(result.totalSchemas).toBe(3); + }); + + it("handles malformed spec file gracefully", async () => { + const specPath = path.join(tmpDir, "spec-0.json"); + await writeFile(specPath, "not valid json {{{"); + await writeFile( + path.join(tmpDir, SPECS_MANIFEST_FILENAME), + JSON.stringify({ specs: [{ type: "openapi", specPath }] }) + ); + + const result = await analyzeSpecs(tmpDir); + expect(result.totalSpecs).toBe(1); + expect(result.totalEndpoints).toBe(0); + expect(result.specs[0]?.title).toBeUndefined(); + }); + + it("handles missing spec file gracefully", async () => { + await writeFile( + path.join(tmpDir, SPECS_MANIFEST_FILENAME), + JSON.stringify({ specs: [{ type: "openapi", specPath: path.join(tmpDir, "missing.json") }] }) + ); + + const result = await analyzeSpecs(tmpDir); + expect(result.totalSpecs).toBe(1); + expect(result.totalEndpoints).toBe(0); + }); +}); + +describe("formatSpecAnalysis", () => { + it("formats analysis with specs", () => { + const output = formatSpecAnalysis({ + totalSpecs: 1, + totalEndpoints: 5, + totalSchemas: 3, + specs: [ + { + type: "openapi", + title: "My API", + version: "1.0.0", + endpointCount: 5, + schemaCount: 3, + specPath: "/fern/specs/spec-0.json" + } + ] + }); + expect(output).toContain("Total specs: 1"); + expect(output).toContain("Total endpoints: 5"); + expect(output).toContain("Total schemas: 3"); + expect(output).toContain("My API"); + expect(output).toContain("1.0.0"); + }); + + it("formats empty analysis", () => { + const output = formatSpecAnalysis({ + totalSpecs: 0, + totalEndpoints: 0, + totalSchemas: 0, + specs: [] + }); + expect(output).toContain("Total specs: 0"); + }); +}); diff --git a/generators/cli/src/__test__/copySpecs.test.ts b/generators/cli/src/__test__/copySpecs.test.ts new file mode 100644 index 000000000000..b5702479b0f8 --- /dev/null +++ b/generators/cli/src/__test__/copySpecs.test.ts @@ -0,0 +1,212 @@ +import { mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises"; +import os from "os"; +import path from "path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { copySpecFile, copySpecs, type RawSpecsManifest } from "../copySpecs.js"; + +describe("copySpecFile", () => { + let tmpDir: string; + + beforeEach(async () => { + tmpDir = await mkdtemp(path.join(os.tmpdir(), "copySpecs-")); + }); + + afterEach(async () => { + await rm(tmpDir, { recursive: true, force: true }); + }); + + it("copies a file and returns relative path", async () => { + const sourceDir = path.join(tmpDir, "source"); + await mkdir(path.join(sourceDir, "api"), { recursive: true }); + await writeFile(path.join(sourceDir, "api", "spec.yaml"), "openapi: 3.0.0"); + + const outputDir = path.join(tmpDir, "output"); + await mkdir(outputDir, { recursive: true }); + + const result = await copySpecFile(path.join(sourceDir, "api", "spec.yaml"), outputDir, sourceDir); + + expect(result).toBe(path.join("api", "spec.yaml")); + const content = await readFile(path.join(outputDir, "api", "spec.yaml"), "utf-8"); + expect(content).toBe("openapi: 3.0.0"); + }); + + it("copies a directory recursively for protobuf", async () => { + const sourceDir = path.join(tmpDir, "source"); + const protoDir = path.join(sourceDir, "proto"); + await mkdir(path.join(protoDir, "service"), { recursive: true }); + await writeFile(path.join(protoDir, "service", "api.proto"), 'syntax = "proto3";'); + + const outputDir = path.join(tmpDir, "output"); + await mkdir(outputDir, { recursive: true }); + + const result = await copySpecFile(protoDir, outputDir, sourceDir); + + expect(result).toBe("proto"); + const content = await readFile(path.join(outputDir, "proto", "service", "api.proto"), "utf-8"); + expect(content).toBe('syntax = "proto3";'); + }); + + it("throws a clear error when source file does not exist", async () => { + const outputDir = path.join(tmpDir, "output"); + await mkdir(outputDir, { recursive: true }); + + await expect(copySpecFile(path.join(tmpDir, "nonexistent.yaml"), outputDir, tmpDir)).rejects.toThrow( + "Spec file not found at mount path" + ); + }); + + it("falls back to basename when path does not start with specsDir", async () => { + const sourceFile = path.join(tmpDir, "random", "spec.yaml"); + await mkdir(path.join(tmpDir, "random"), { recursive: true }); + await writeFile(sourceFile, "test"); + + const outputDir = path.join(tmpDir, "output"); + await mkdir(outputDir, { recursive: true }); + + const result = await copySpecFile(sourceFile, outputDir, "/nonexistent"); + + expect(result).toBe("spec.yaml"); + }); +}); + +describe("copySpecs", () => { + let tmpDir: string; + + beforeEach(async () => { + tmpDir = await mkdtemp(path.join(os.tmpdir(), "copySpecs-")); + }); + + afterEach(async () => { + await rm(tmpDir, { recursive: true, force: true }); + }); + + it("does nothing when no manifest exists", async () => { + const outputDir = path.join(tmpDir, "output"); + await mkdir(outputDir, { recursive: true }); + + // Should not throw + await copySpecs(outputDir, path.join(tmpDir, "nonexistent")); + + // specs/ dir should not be created + try { + await readFile(path.join(outputDir, "specs", "specs-manifest.json"), "utf-8"); + expect.fail("Should not have created specs directory"); + } catch { + // expected + } + }); + + it("does nothing when manifest has empty specs array", async () => { + const rawSpecsDir = path.join(tmpDir, "specs"); + await mkdir(rawSpecsDir, { recursive: true }); + const manifest: RawSpecsManifest = { specs: [] }; + await writeFile(path.join(rawSpecsDir, "specs-manifest.json"), JSON.stringify(manifest)); + + const outputDir = path.join(tmpDir, "output"); + await mkdir(outputDir, { recursive: true }); + + await copySpecs(outputDir, rawSpecsDir); + + // specs/ dir should not be created + try { + await readFile(path.join(outputDir, "specs", "specs-manifest.json"), "utf-8"); + expect.fail("Should not have created specs directory"); + } catch { + // expected + } + }); + + it("copies OpenAPI spec and writes output manifest", async () => { + const rawSpecsDir = path.join(tmpDir, "specs"); + await mkdir(path.join(rawSpecsDir, "api"), { recursive: true }); + await writeFile(path.join(rawSpecsDir, "api", "openapi.yaml"), "openapi: 3.0.0"); + + const manifest: RawSpecsManifest = { + specs: [ + { + type: "openapi", + specPath: path.join(rawSpecsDir, "api", "openapi.yaml") + } + ] + }; + await writeFile(path.join(rawSpecsDir, "specs-manifest.json"), JSON.stringify(manifest)); + + const outputDir = path.join(tmpDir, "output"); + await mkdir(outputDir, { recursive: true }); + + await copySpecs(outputDir, rawSpecsDir); + + // Verify spec file was copied + const specContent = await readFile(path.join(outputDir, "specs", "api", "openapi.yaml"), "utf-8"); + expect(specContent).toBe("openapi: 3.0.0"); + + // Verify output manifest was written with relative paths + const outputManifest: RawSpecsManifest = JSON.parse( + await readFile(path.join(outputDir, "specs", "specs-manifest.json"), "utf-8") + ); + expect(outputManifest.specs).toHaveLength(1); + expect(outputManifest.specs[0]?.type).toBe("openapi"); + expect(outputManifest.specs[0]?.specPath).toBe(path.join("api", "openapi.yaml")); + }); + + it("copies spec with overrides", async () => { + const rawSpecsDir = path.join(tmpDir, "specs"); + await mkdir(path.join(rawSpecsDir, "api"), { recursive: true }); + await mkdir(path.join(rawSpecsDir, "overrides"), { recursive: true }); + + await writeFile(path.join(rawSpecsDir, "api", "spec.yaml"), "spec"); + await writeFile(path.join(rawSpecsDir, "overrides", "override.yaml"), "override"); + + const manifest: RawSpecsManifest = { + specs: [ + { + type: "openapi", + specPath: path.join(rawSpecsDir, "api", "spec.yaml"), + overridePaths: [path.join(rawSpecsDir, "overrides", "override.yaml")] + } + ] + }; + await writeFile(path.join(rawSpecsDir, "specs-manifest.json"), JSON.stringify(manifest)); + + const outputDir = path.join(tmpDir, "output"); + await mkdir(outputDir, { recursive: true }); + + await copySpecs(outputDir, rawSpecsDir); + + // Verify all files were copied + expect(await readFile(path.join(outputDir, "specs", "api", "spec.yaml"), "utf-8")).toBe("spec"); + expect(await readFile(path.join(outputDir, "specs", "overrides", "override.yaml"), "utf-8")).toBe("override"); + + // Verify output manifest has correct relative paths + const outputManifest: RawSpecsManifest = JSON.parse( + await readFile(path.join(outputDir, "specs", "specs-manifest.json"), "utf-8") + ); + expect(outputManifest.specs[0]?.overridePaths).toHaveLength(1); + }); + + it("copies protobuf directory spec", async () => { + const rawSpecsDir = path.join(tmpDir, "specs"); + const protoDir = path.join(rawSpecsDir, "proto"); + await mkdir(path.join(protoDir, "service"), { recursive: true }); + await writeFile(path.join(protoDir, "service", "api.proto"), 'syntax = "proto3";'); + + const manifest: RawSpecsManifest = { + specs: [ + { + type: "protobuf", + specPath: path.join(rawSpecsDir, "proto") + } + ] + }; + await writeFile(path.join(rawSpecsDir, "specs-manifest.json"), JSON.stringify(manifest)); + + const outputDir = path.join(tmpDir, "output"); + await mkdir(outputDir, { recursive: true }); + + await copySpecs(outputDir, rawSpecsDir); + + // Verify protobuf directory was copied recursively + const protoContent = await readFile(path.join(outputDir, "specs", "proto", "service", "api.proto"), "utf-8"); + expect(protoContent).toBe('syntax = "proto3";'); + }); +}); diff --git a/generators/cli/src/analyzeSpecs.ts b/generators/cli/src/analyzeSpecs.ts new file mode 100644 index 000000000000..51fca7b55d8f --- /dev/null +++ b/generators/cli/src/analyzeSpecs.ts @@ -0,0 +1,163 @@ +import { readFile } from "fs/promises"; +import path from "path"; +import { + type RawSpecsManifest, + type RawSpecsManifestEntry, + SPECS_DIRECTORY, + SPECS_MANIFEST_FILENAME +} from "./copySpecs.js"; + +interface SpecSummary { + type: RawSpecsManifestEntry["type"]; + title: string | undefined; + version: string | undefined; + endpointCount: number; + schemaCount: number; + specPath: string; +} + +export interface SpecAnalysis { + totalSpecs: number; + totalEndpoints: number; + totalSchemas: number; + specs: SpecSummary[]; +} + +export async function analyzeSpecs(rawSpecsDir?: string): Promise { + const specsDir = rawSpecsDir ?? SPECS_DIRECTORY; + const manifestPath = path.join(specsDir, SPECS_MANIFEST_FILENAME); + + let manifestContent: string; + try { + manifestContent = await readFile(manifestPath, "utf-8"); + } catch { + return { totalSpecs: 0, totalEndpoints: 0, totalSchemas: 0, specs: [] }; + } + + const manifest: RawSpecsManifest = JSON.parse(manifestContent); + if (manifest.specs.length === 0) { + return { totalSpecs: 0, totalEndpoints: 0, totalSchemas: 0, specs: [] }; + } + + const specs: SpecSummary[] = []; + let totalEndpoints = 0; + let totalSchemas = 0; + + for (const entry of manifest.specs) { + const summary = await analyzeSpecEntry(entry); + totalEndpoints += summary.endpointCount; + totalSchemas += summary.schemaCount; + specs.push(summary); + } + + return { + totalSpecs: specs.length, + totalEndpoints, + totalSchemas, + specs + }; +} + +async function analyzeSpecEntry(entry: RawSpecsManifestEntry): Promise { + if (entry.type === "protobuf" || entry.type === "graphql") { + return { + type: entry.type, + title: undefined, + version: undefined, + endpointCount: 0, + schemaCount: 0, + specPath: entry.specPath + }; + } + + try { + const content = await readFile(entry.specPath, "utf-8"); + const spec = JSON.parse(content); + return analyzeOpenAPILike(spec, entry); + } catch { + return { + type: entry.type, + title: undefined, + version: undefined, + endpointCount: 0, + schemaCount: 0, + specPath: entry.specPath + }; + } +} + +const HTTP_METHODS = new Set(["get", "post", "put", "patch", "delete", "head", "options", "trace"]); + +function analyzeOpenAPILike(spec: Record, entry: RawSpecsManifestEntry): SpecSummary { + const info = spec.info as Record | undefined; + const title = typeof info?.title === "string" ? info.title : undefined; + const version = typeof info?.version === "string" ? info.version : undefined; + + let endpointCount = 0; + const paths = spec.paths as Record> | undefined; + if (paths != null) { + for (const pathItem of Object.values(paths)) { + if (pathItem != null && typeof pathItem === "object") { + for (const key of Object.keys(pathItem)) { + if (HTTP_METHODS.has(key.toLowerCase())) { + endpointCount++; + } + } + } + } + } + + let schemaCount = 0; + const components = spec.components as Record | undefined; + const schemas = components?.schemas as Record | undefined; + if (schemas != null) { + schemaCount = Object.keys(schemas).length; + } + + // AsyncAPI channels + if (entry.type === "asyncapi") { + const channels = spec.channels as Record | undefined; + if (channels != null) { + endpointCount = Object.keys(channels).length; + } + } + + // OpenRPC methods + if (entry.type === "openrpc") { + const methods = spec.methods as unknown[] | undefined; + if (Array.isArray(methods)) { + endpointCount = methods.length; + } + } + + return { type: entry.type, title, version, endpointCount, schemaCount, specPath: entry.specPath }; +} + +export function formatSpecAnalysis(analysis: SpecAnalysis): string { + const lines: string[] = []; + lines.push("╔══════════════════════════════════════════════════════════╗"); + lines.push("║ Fern CLI Generator — Spec Analysis ║"); + lines.push("╚══════════════════════════════════════════════════════════╝"); + lines.push(""); + lines.push(` Total specs: ${analysis.totalSpecs}`); + lines.push(` Total endpoints: ${analysis.totalEndpoints}`); + lines.push(` Total schemas: ${analysis.totalSchemas}`); + lines.push(""); + + for (const [i, spec] of analysis.specs.entries()) { + lines.push(` ── Spec ${i + 1} ──────────────────────────────────────────`); + lines.push(` Type: ${spec.type}`); + if (spec.title != null) { + lines.push(` Title: ${spec.title}`); + } + if (spec.version != null) { + lines.push(` Version: ${spec.version}`); + } + lines.push(` Endpoints: ${spec.endpointCount}`); + lines.push(` Schemas: ${spec.schemaCount}`); + lines.push(` Path: ${spec.specPath}`); + lines.push(""); + } + + return lines.join("\n"); +} diff --git a/generators/cli/src/cli.ts b/generators/cli/src/cli.ts index 1ec296c01c88..94ff784e1ae7 100644 --- a/generators/cli/src/cli.ts +++ b/generators/cli/src/cli.ts @@ -4,8 +4,9 @@ import { GeneratorUpdate, parseGeneratorConfig } from "@fern-api/base-generator"; -import { cp, mkdir } from "fs/promises"; -import path from "path"; +import { mkdir } from "fs/promises"; +import { analyzeSpecs, formatSpecAnalysis } from "./analyzeSpecs.js"; +import { copySpecs } from "./copySpecs.js"; const pathToConfig = process.argv[process.argv.length - 1]; if (pathToConfig == null) { @@ -29,7 +30,18 @@ async function generate(configPath: string): Promise { const outputDir = config.output.path; await mkdir(outputDir, { recursive: true }); - await cp(path.join(__dirname, "sdk"), outputDir, { recursive: true }); + // Analyze and print spec summary + const analysis = await analyzeSpecs(); + if (analysis.totalSpecs > 0) { + // biome-ignore lint/suspicious/noConsole: generator CLI output + console.log(formatSpecAnalysis(analysis)); + } else { + // biome-ignore lint/suspicious/noConsole: generator CLI output + console.log("No API specs were mounted."); + } + + // Copy API spec files from the mounted directory to the output + await copySpecs(outputDir); await generatorLoggingClient.sendUpdate(GeneratorUpdate.exitStatusUpdate(ExitStatusUpdate.successful({}))); } catch (e) { diff --git a/generators/cli/src/copySpecs.ts b/generators/cli/src/copySpecs.ts new file mode 100644 index 000000000000..569391d44a70 --- /dev/null +++ b/generators/cli/src/copySpecs.ts @@ -0,0 +1,82 @@ +import { cp, lstat, mkdir, readFile, writeFile } from "fs/promises"; +import path from "path"; + +export interface RawSpecsManifestEntry { + type: "openapi" | "asyncapi" | "protobuf" | "openrpc" | "graphql"; + specPath: string; + overridePaths?: string[]; +} + +export interface RawSpecsManifest { + specs: RawSpecsManifestEntry[]; +} + +export const SPECS_DIRECTORY = "/fern/specs"; +export const SPECS_MANIFEST_FILENAME = "specs-manifest.json"; + +export async function copySpecs(outputDir: string, rawSpecsDir?: string): Promise { + const specsDir = rawSpecsDir ?? SPECS_DIRECTORY; + const manifestPath = path.join(specsDir, SPECS_MANIFEST_FILENAME); + let manifestContent: string; + try { + manifestContent = await readFile(manifestPath, "utf-8"); + } catch { + // No manifest means no raw specs were mounted + return; + } + + const manifest: RawSpecsManifest = JSON.parse(manifestContent); + if (manifest.specs.length === 0) { + return; + } + + const specsOutputDir = path.join(outputDir, "specs"); + await mkdir(specsOutputDir, { recursive: true }); + + const copiedManifest: RawSpecsManifest = { specs: [] }; + + for (const entry of manifest.specs) { + const copiedEntry: RawSpecsManifestEntry = { + type: entry.type, + specPath: await copySpecFile(entry.specPath, specsOutputDir, specsDir) + }; + + if (entry.overridePaths != null && entry.overridePaths.length > 0) { + copiedEntry.overridePaths = []; + for (const overridePath of entry.overridePaths) { + copiedEntry.overridePaths.push(await copySpecFile(overridePath, specsOutputDir, specsDir)); + } + } + + copiedManifest.specs.push(copiedEntry); + } + + await writeFile(path.join(specsOutputDir, SPECS_MANIFEST_FILENAME), JSON.stringify(copiedManifest, undefined, 4)); +} + +export async function copySpecFile(containerPath: string, specsOutputDir: string, specsDir?: string): Promise { + const baseDir = specsDir ?? SPECS_DIRECTORY; + const relativePath = containerPath.startsWith(baseDir + "/") + ? containerPath.slice(baseDir.length + 1) + : path.basename(containerPath); + + const destPath = path.join(specsOutputDir, relativePath); + let isDir = false; + try { + const stat = await lstat(containerPath); + isDir = stat.isDirectory(); + } catch (err: unknown) { + if (err != null && typeof err === "object" && "code" in err && err.code === "ENOENT") { + throw new Error(`Spec file not found at mount path: ${containerPath}`); + } + throw err; + } + if (isDir) { + await cp(containerPath, destPath, { recursive: true }); + } else { + await mkdir(path.dirname(destPath), { recursive: true }); + await cp(containerPath, destPath); + } + + return relativePath; +} diff --git a/generators/cli/src/index.ts b/generators/cli/src/index.ts index cb0ff5c3b541..384ec86a8133 100644 --- a/generators/cli/src/index.ts +++ b/generators/cli/src/index.ts @@ -1 +1,2 @@ -export {}; +export { analyzeSpecs, formatSpecAnalysis, type SpecAnalysis } from "./analyzeSpecs.js"; +export { copySpecFile, copySpecs, type RawSpecsManifest, type RawSpecsManifestEntry } from "./copySpecs.js"; diff --git a/generators/cli/versions.yml b/generators/cli/versions.yml index acfa0623e3e3..a3e3cbdd3b36 100644 --- a/generators/cli/versions.yml +++ b/generators/cli/versions.yml @@ -1,4 +1,13 @@ # yaml-language-server: $schema=../../fern-versions-yml.schema.json +- version: 0.2.0 + changelogEntry: + - summary: | + Add API spec pre-processing infrastructure: bundle external $refs, + merge overrides, apply overlays, and output compact JSON for embedding + in generated CLI. + type: feat + createdAt: "2026-05-20" + irVersion: 66 - version: 0.1.0 changelogEntry: - summary: | diff --git a/generators/cli/vitest.config.ts b/generators/cli/vitest.config.ts new file mode 100644 index 000000000000..efbce2018779 --- /dev/null +++ b/generators/cli/vitest.config.ts @@ -0,0 +1 @@ +export { default } from "@fern-api/configs/vitest/base.mjs"; diff --git a/generators/php/base/src/asIs/Client/RawClientTest.Template.php b/generators/php/base/src/asIs/Client/RawClientTest.Template.php index 52a256a61414..c23dc6e24362 100644 --- a/generators/php/base/src/asIs/Client/RawClientTest.Template.php +++ b/generators/php/base/src/asIs/Client/RawClientTest.Template.php @@ -1066,4 +1066,143 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void {}, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void {}, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/generators/php/base/src/asIs/Client/RetryDecoratingClient.Template.php b/generators/php/base/src/asIs/Client/RetryDecoratingClient.Template.php index 4511848805db..af9d001e5369 100644 --- a/generators/php/base/src/asIs/Client/RetryDecoratingClient.Template.php +++ b/generators/php/base/src/asIs/Client/RetryDecoratingClient.Template.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/generators/php/sdk/changes/2.10.2/fix-interface-exists-guzzle.yml b/generators/php/sdk/changes/2.10.2/fix-interface-exists-guzzle.yml new file mode 100644 index 000000000000..78a6a7f5d065 --- /dev/null +++ b/generators/php/sdk/changes/2.10.2/fix-interface-exists-guzzle.yml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json + +- summary: | + Use `interface_exists` instead of `class_exists` when checking for + `GuzzleHttp\ClientInterface` in the retry client timeout logic. + `class_exists` always returns false for interfaces, so Guzzle timeout + support was never detected. + type: fix diff --git a/generators/php/sdk/versions.yml b/generators/php/sdk/versions.yml index ff5fc5b4b109..fbcd74f9bb6d 100644 --- a/generators/php/sdk/versions.yml +++ b/generators/php/sdk/versions.yml @@ -1,4 +1,14 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 2.10.2 + changelogEntry: + - summary: | + Use `interface_exists` instead of `class_exists` when checking for + `GuzzleHttp\ClientInterface` in the retry client timeout logic. + `class_exists` always returns false for interfaces, so Guzzle timeout + support was never detected. + type: fix + createdAt: "2026-05-20" + irVersion: 66 - version: 2.10.1 changelogEntry: - summary: | diff --git a/package.json b/package.json index 50e3c0bf6c51..9ba10fe65b9a 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "generator-cli:generate": "pnpm fern:build && pnpm fern generate --api generator-cli --local && turbo run dist:cli --filter=@fern-api/generator-cli", "seed:build": "turbo run dist:cli --filter=@fern-api/seed-cli && echo 'Run node --enable-source-maps packages/seed/dist/cli.cjs'", "publish": "pnpm -r publish --access public --no-git-checks --loglevel=verbose", - "jsonschema": "pnpm definition-yml:jsonschema && pnpm api-yml:jsonschema && pnpm package-yml:jsonschema && pnpm docs-yml:jsonschema && pnpm generators-yml:jsonschema && pnpm versions-yml:jsonschema && pnpm products-yml:jsonschema && pnpm product-yml:jsonschema && pnpm version-yml:jsonschema", + "jsonschema": "pnpm definition-yml:jsonschema && pnpm api-yml:jsonschema && pnpm package-yml:jsonschema && pnpm docs-yml:jsonschema && pnpm generators-yml:jsonschema && pnpm versions-yml:jsonschema && pnpm products-yml:jsonschema && pnpm product-yml:jsonschema && pnpm version-yml:jsonschema && pnpm fern-yml:jsonschema", + "fern-yml:jsonschema": "pnpm fern beta schema --output fern-yml.schema.json", "definition-yml:jsonschema": "pnpm fern jsonschema fern.schema.json --api fern-definition --type file.DefinitionFileSchema && pnpm fern jsonschema packages/cli/workspace/lazy-fern-workspace/src/fern.schema.json --api fern-definition --type file.DefinitionFileSchema", "api-yml:jsonschema": "pnpm fern jsonschema api-yml.schema.json --api fern-definition --type file.RootApiFileSchema && pnpm fern jsonschema packages/cli/workspace/lazy-fern-workspace/src/api-yml.schema.json --api fern-definition --type file.RootApiFileSchema", "package-yml:jsonschema": "pnpm fern jsonschema package-yml.schema.json --api fern-definition --type file.PackageMarkerFileSchema && pnpm fern jsonschema packages/cli/workspace/lazy-fern-workspace/src/package-yml.schema.json --api fern-definition --type file.PackageMarkerFileSchema", diff --git a/packages/cli/cli-v2/src/commands/schema/__test__/command.test.ts b/packages/cli/cli-v2/src/commands/schema/__test__/command.test.ts index f71a279a191d..38c7e6acf743 100644 --- a/packages/cli/cli-v2/src/commands/schema/__test__/command.test.ts +++ b/packages/cli/cli-v2/src/commands/schema/__test__/command.test.ts @@ -1,7 +1,7 @@ import { AbsoluteFilePath } from "@fern-api/fs-utils"; import { CliError } from "@fern-api/task-context"; import { randomUUID } from "crypto"; -import { mkdir, rm } from "fs/promises"; +import { mkdir, readFile, rm } from "fs/promises"; import { tmpdir } from "os"; import { join } from "path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; @@ -108,4 +108,20 @@ describe("fern schema", () => { expect(getStderr()).toBe(""); }); + + it("writes the full schema to a file when --output is set", async () => { + const outputPath = join(testDir, "fern-yml.schema.json"); + const { context, getStdout } = await createTestContextWithCapture({ cwd: testDir }); + const cmd = new SchemaCommand(); + + await cmd.handle(context, { "log-level": "info", pretty: true, output: outputPath }); + + expect(getStdout()).toBe(""); + + const written = await readFile(outputPath, "utf-8"); + const parsed = JSON.parse(written); + expect(parsed.type).toBe("object"); + expect(parsed.properties.org).toBeDefined(); + expect(written.endsWith("\n")).toBe(true); + }); }); diff --git a/packages/cli/cli-v2/src/commands/schema/command.ts b/packages/cli/cli-v2/src/commands/schema/command.ts index 160bfcf6cfd0..37b963b19e70 100644 --- a/packages/cli/cli-v2/src/commands/schema/command.ts +++ b/packages/cli/cli-v2/src/commands/schema/command.ts @@ -1,5 +1,8 @@ import { getFernYmlJsonSchema, getJsonSchemaByName, getJsonSchemaNames } from "@fern-api/config"; +import { dirname, doesPathExist, resolve } from "@fern-api/fs-utils"; import { CliError } from "@fern-api/task-context"; +import chalk from "chalk"; +import { mkdir, writeFile } from "fs/promises"; import type { Argv } from "yargs"; import type { Context } from "../../context/Context.js"; @@ -15,6 +18,8 @@ export declare namespace SchemaCommand { name?: string; /** Pretty-print JSON output. Defaults to true. */ pretty: boolean; + /** Write JSON Schema to this path instead of stdout. */ + output?: string; } } @@ -27,30 +32,57 @@ export declare namespace SchemaCommand { * (e.g. `api`, `sdks.targets`), prints just that subsection. * * Output is pure JSON on stdout so it is pipe-safe (e.g. - * `fern schema | jq .properties.sdks`). + * `fern schema | jq .properties.sdks`). Use `--output` to write a file + * (e.g. for CI: `fern schema --output fern-yml.schema.json`). */ export class SchemaCommand { public async handle(context: Context, args: SchemaCommand.Args): Promise { - if (args.name == null) { - this.writeJson(context, getFernYmlJsonSchema(), args.pretty); + const schema = this.resolveSchema(args.name); + + if (args.output != null) { + await this.writeToFile(context, schema, args.pretty, args.output); return; } - const schema = getJsonSchemaByName(args.name); + this.writeToStdout(context, schema, args.pretty); + } + + private resolveSchema(name: string | undefined): Record { + if (name == null) { + return getFernYmlJsonSchema(); + } + + const schema = getJsonSchemaByName(name); if (schema == null) { const available = getJsonSchemaNames().join(", "); throw new CliError({ - message: `Unknown schema '${args.name}'. Available subsections: ${available}.`, + message: `Unknown schema '${name}'. Available subsections: ${available}.`, code: CliError.Code.ConfigError }); } - this.writeJson(context, schema, args.pretty); + return schema; } - private writeJson(context: Context, value: unknown, pretty: boolean): void { + private formatJson(value: unknown, pretty: boolean): string { const json = pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value); - context.stdout.info(json); + return `${json}\n`; + } + + private writeToStdout(context: Context, value: unknown, pretty: boolean): void { + context.stdout.info(this.formatJson(value, pretty).trimEnd()); + } + + private async writeToFile(context: Context, value: unknown, pretty: boolean, outputPath: string): Promise { + const absoluteOutputPath = resolve(context.cwd, outputPath); + const outputDir = dirname(absoluteOutputPath); + + if (!(await doesPathExist(outputDir))) { + await mkdir(outputDir, { recursive: true }); + } + + await writeFile(absoluteOutputPath, this.formatJson(value, pretty)); + context.stderr.info(chalk.green(`Wrote JSON Schema to ${absoluteOutputPath}`)); } } @@ -69,6 +101,10 @@ export function addSchemaCommand(cli: Argv): void { "Dot-delimited subsection of fern.yml. Omit to print the full schema. " + `Available subsections: ${getJsonSchemaNames().join(", ")}.` }) + .option("output", { + type: "string", + description: "Write JSON Schema to this file instead of stdout" + }) .option("pretty", { type: "boolean", default: true, diff --git a/packages/cli/cli-v2/src/ui/TtyAwareLogger.ts b/packages/cli/cli-v2/src/ui/TtyAwareLogger.ts index 249229296606..a11c894cf1a7 100644 --- a/packages/cli/cli-v2/src/ui/TtyAwareLogger.ts +++ b/packages/cli/cli-v2/src/ui/TtyAwareLogger.ts @@ -44,6 +44,17 @@ export class TtyAwareLogger { this.stdout = stdout; this.stderr = stderr; this.isTTY = stdout.isTTY === true && !IS_CI; + + // Downstream pipe readers can close early (`fern ... | head`), which + // emits EPIPE on stdout/stderr. Treat that as normal CLI I/O behavior. + for (const stream of [this.stdout, this.stderr]) { + stream.on("error", (error: Error) => { + if (isEpipeError(error)) { + return; + } + throw error; + }); + } } /** @@ -161,3 +172,11 @@ export class TtyAwareLogger { return paint; } } + +function isEpipeError(error: Error): boolean { + return hasErrnoCode(error) && error.code === "EPIPE"; +} + +function hasErrnoCode(error: Error): error is Error & { code: string } { + return "code" in error && typeof error.code === "string"; +} diff --git a/packages/cli/cli-v2/src/ui/__test__/TtyAwareLogger.test.ts b/packages/cli/cli-v2/src/ui/__test__/TtyAwareLogger.test.ts index bcd7754e0a77..f77983ca7d97 100644 --- a/packages/cli/cli-v2/src/ui/__test__/TtyAwareLogger.test.ts +++ b/packages/cli/cli-v2/src/ui/__test__/TtyAwareLogger.test.ts @@ -95,6 +95,27 @@ describe("TtyAwareLogger", () => { }); }); + describe("stream errors", () => { + it("swallows EPIPE from stdout and stderr", () => { + const stdout = createStream({ isTTY: false }); + const stderr = createStream({ isTTY: false }); + new TtyAwareLogger(stdout.stream, stderr.stream); + + const error = Object.assign(new Error("write EPIPE"), { code: "EPIPE" }); + + expect(() => stdout.stream.emit("error", error)).not.toThrow(); + expect(() => stderr.stream.emit("error", error)).not.toThrow(); + }); + + it("rethrows non-EPIPE stream errors", () => { + const stdout = createStream({ isTTY: false }); + const stderr = createStream({ isTTY: false }); + new TtyAwareLogger(stdout.stream, stderr.stream); + + expect(() => stdout.stream.emit("error", new Error("boom"))).toThrow("boom"); + }); + }); + describe("register", () => { it("does not start a paint loop on non-TTY streams", () => { const stdout = createStream({ isTTY: false }); diff --git a/packages/cli/cli/changes/5.25.0/add-cli-generator.yml b/packages/cli/cli/changes/5.25.0/add-cli-generator.yml index c9e5f23726b9..7877edfd98d6 100644 --- a/packages/cli/cli/changes/5.25.0/add-cli-generator.yml +++ b/packages/cli/cli/changes/5.25.0/add-cli-generator.yml @@ -1,3 +1,6 @@ - summary: | Register the new `fernapi/fern-cli` generator in the CLI configuration. type: feat +- summary: | + Pass raw API spec files (OAS, overrides, overlays, protobuf, etc.) to generators via Docker mount. + type: feat diff --git a/packages/cli/cli/changes/5.33.0/add-raw-spec-mounting.yml b/packages/cli/cli/changes/5.33.0/add-raw-spec-mounting.yml new file mode 100644 index 000000000000..22472531f97d --- /dev/null +++ b/packages/cli/cli/changes/5.33.0/add-raw-spec-mounting.yml @@ -0,0 +1,6 @@ +- summary: | + Mount pre-processed API specs into generator Docker containers. + Specs are bundled, overrides merged, overlays applied, x-fern-ignore + operations filtered, and x-fern-audiences respected before outputting + compact JSON to generators. Add hidden `resolve-specs` command. + type: feat diff --git a/packages/cli/cli/changes/5.33.1/publish-fern-yml-jsonschema.yml b/packages/cli/cli/changes/5.33.1/publish-fern-yml-jsonschema.yml new file mode 100644 index 000000000000..c6c686c7f236 --- /dev/null +++ b/packages/cli/cli/changes/5.33.1/publish-fern-yml-jsonschema.yml @@ -0,0 +1,5 @@ +- summary: | + Publish a JSON Schema for `fern.yml` so YAML language servers can provide + autocomplete and validation in IDEs. Regenerate via `fern schema --output` + (or `pnpm fern-yml:jsonschema` in the monorepo). + type: chore diff --git a/packages/cli/cli/changes/5.33.2/validate-custom-domain-basepath.yml b/packages/cli/cli/changes/5.33.2/validate-custom-domain-basepath.yml new file mode 100644 index 000000000000..57af7a229556 --- /dev/null +++ b/packages/cli/cli/changes/5.33.2/validate-custom-domain-basepath.yml @@ -0,0 +1,11 @@ +- summary: | + Validate that the Fern instance `url` and every `custom-domain` share the same basepath when + basepath-aware mode is enabled (`multi-source: true` or the deprecated + `experimental.basepath-aware: true`). Previously the check was skipped when the custom domain + was at the root, allowing publishes that would 404 after DNS cutover. + type: fix +- summary: | + Strip any `https://` or `http://` prefix from `custom-domain` entries in `docs.yml` before + they're sent to FDR or used for basepath comparisons. Including the protocol in the configured + value could interfere with basepath resolution during DNS cutover. + type: fix diff --git a/packages/cli/cli/changes/5.33.3/fix-cli-v2-epipe.yml b/packages/cli/cli/changes/5.33.3/fix-cli-v2-epipe.yml new file mode 100644 index 000000000000..913e73cead19 --- /dev/null +++ b/packages/cli/cli/changes/5.33.3/fix-cli-v2-epipe.yml @@ -0,0 +1,3 @@ +- summary: | + Swallow stdout/stderr EPIPE events in the cli-v2 terminal logger so Unix pipe closures do not report false-positive internal errors. + type: fix diff --git a/packages/cli/cli/changes/5.33.3/fix-ir-to-fdr-server-validation.yml b/packages/cli/cli/changes/5.33.3/fix-ir-to-fdr-server-validation.yml new file mode 100644 index 000000000000..6b5bc9b931a3 --- /dev/null +++ b/packages/cli/cli/changes/5.33.3/fix-ir-to-fdr-server-validation.yml @@ -0,0 +1,3 @@ +- summary: | + Stop reporting invalid endpoint server references during IR-to-FDR conversion to Sentry. + type: fix diff --git a/packages/cli/cli/changes/5.33.3/fix-posthog-distinct-id-storage.yml b/packages/cli/cli/changes/5.33.3/fix-posthog-distinct-id-storage.yml new file mode 100644 index 000000000000..92da79f4de5c --- /dev/null +++ b/packages/cli/cli/changes/5.33.3/fix-posthog-distinct-id-storage.yml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json + +- summary: | + Prevent CLI telemetry from failing when the user's home directory cannot store Fern's analytics ID. + type: fix diff --git a/packages/cli/cli/src/cli.ts b/packages/cli/cli/src/cli.ts index 4b76ac50fa23..ba56f9514c11 100644 --- a/packages/cli/cli/src/cli.ts +++ b/packages/cli/cli/src/cli.ts @@ -85,6 +85,7 @@ import { mergeOpenAPIWithOverrides } from "./commands/merge/mergeOpenAPIWithOver import { mockServer } from "./commands/mock/mockServer.js"; import { registerWorkspacesV1 } from "./commands/register/registerWorkspacesV1.js"; import { registerWorkspacesV2 } from "./commands/register/registerWorkspacesV2.js"; +import { resolveSpecsForWorkspaces } from "./commands/resolve-specs/resolveSpecsForWorkspaces.js"; import { sdkDiffCommand } from "./commands/sdk-diff/sdkDiffCommand.js"; import type { SdkPreviewResult, SdkPreviewSuccess } from "./commands/sdk-preview/sdkPreview.js"; import { sdkPreview } from "./commands/sdk-preview/sdkPreview.js"; @@ -257,6 +258,7 @@ async function tryRunCli(cliContext: CliContext) { addIrCommand(cli, cliContext); addFdrCommand(cli, cliContext); addOpenAPIIrCommand(cli, cliContext); + addResolveSpecsCommand(cli, cliContext); addDynamicIrCommand(cli, cliContext); addValidateCommand(cli, cliContext); addRegisterCommand(cli, cliContext); @@ -1115,6 +1117,34 @@ function addOpenAPIIrCommand(cli: Argv, cliContext: CliContext ); } +function addResolveSpecsCommand(cli: Argv, cliContext: CliContext) { + cli.command( + "resolve-specs ", + false, + (yargs) => + yargs + .positional("path-to-output", { + type: "string", + description: "Path to write resolved specs", + demandOption: true + }) + .option("api", { + string: true, + description: "Only run the command on the provided API" + }), + async (argv) => { + await resolveSpecsForWorkspaces({ + project: await loadProjectAndRegisterWorkspacesWithContext(cliContext, { + commandLineApiWorkspace: argv.api, + defaultToAllApiWorkspaces: true + }), + outputDir: AbsoluteFilePath.of(resolve(cwd(), argv.pathToOutput)), + cliContext + }); + } + ); +} + function addDynamicIrCommand(cli: Argv, cliContext: CliContext) { cli.command( "dynamic-ir ", diff --git a/packages/cli/cli/src/commands/resolve-specs/resolveSpecsForWorkspaces.ts b/packages/cli/cli/src/commands/resolve-specs/resolveSpecsForWorkspaces.ts new file mode 100644 index 000000000000..cb6cdff83e20 --- /dev/null +++ b/packages/cli/cli/src/commands/resolve-specs/resolveSpecsForWorkspaces.ts @@ -0,0 +1,56 @@ +import { AbsoluteFilePath } from "@fern-api/fs-utils"; +import { OSSWorkspace } from "@fern-api/lazy-fern-workspace"; +import { collectRawSpecs, SPECS_MANIFEST_FILENAME } from "@fern-api/local-workspace-runner"; +import { Project } from "@fern-api/project-loader"; +import { mkdir, writeFile } from "fs/promises"; +import path from "path"; + +import { CliContext } from "../../cli-context/CliContext.js"; + +export async function resolveSpecsForWorkspaces({ + project, + outputDir, + cliContext +}: { + project: Project; + outputDir: AbsoluteFilePath; + cliContext: CliContext; +}): Promise { + await Promise.all( + project.apiWorkspaces.map(async (workspace) => { + await cliContext.runTaskForWorkspace(workspace, async (context) => { + if (!(workspace instanceof OSSWorkspace)) { + context.logger.info("Skipping, API is specified as a Fern Definition."); + return; + } + + const specs = workspace.allSpecs; + if (specs.length === 0) { + context.logger.info("No specs found."); + return; + } + + const workspaceOutputDir = + project.apiWorkspaces.length > 1 + ? AbsoluteFilePath.of(path.join(outputDir, workspace.workspaceName ?? "api")) + : outputDir; + + await mkdir(workspaceOutputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs, + hostOutputDir: workspaceOutputDir, + containerBaseDir: ".", + context + }); + + await writeFile( + path.join(workspaceOutputDir, SPECS_MANIFEST_FILENAME), + JSON.stringify(manifest, undefined, 4) + ); + + context.logger.info(`Resolved ${manifest.specs.length} spec(s) to ${workspaceOutputDir}`); + }); + }) + ); +} diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index 8c2d5bb92991..f60a43e058bc 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,4 +1,51 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 5.33.3 + changelogEntry: + - summary: | + Swallow stdout/stderr EPIPE events in the cli-v2 terminal logger so Unix pipe closures do not report false-positive internal errors. + type: fix + - summary: | + Stop reporting invalid endpoint server references during IR-to-FDR conversion to Sentry. + type: fix + - summary: | + Prevent CLI telemetry from failing when the user's home directory cannot store Fern's analytics ID. + type: fix + createdAt: "2026-05-20" + irVersion: 66 +- version: 5.33.2 + changelogEntry: + - summary: | + Validate that the Fern instance `url` and every `custom-domain` share the same basepath when + basepath-aware mode is enabled (`multi-source: true` or the deprecated + `experimental.basepath-aware: true`). Previously the check was skipped when the custom domain + was at the root, allowing publishes that would 404 after DNS cutover. + type: fix + - summary: | + Strip any `https://` or `http://` prefix from `custom-domain` entries in `docs.yml` before + they're sent to FDR or used for basepath comparisons. Including the protocol in the configured + value could interfere with basepath resolution during DNS cutover. + type: fix + createdAt: "2026-05-20" + irVersion: 66 +- version: 5.33.1 + changelogEntry: + - summary: | + Publish a JSON Schema for `fern.yml` so YAML language servers can provide + autocomplete and validation in IDEs. Regenerate via `fern schema --output` + (or `pnpm fern-yml:jsonschema` in the monorepo). + type: chore + createdAt: "2026-05-20" + irVersion: 66 +- version: 5.33.0 + changelogEntry: + - summary: | + Mount pre-processed API specs into generator Docker containers. + Specs are bundled, overrides merged, overlays applied, x-fern-ignore + operations filtered, and x-fern-audiences respected before outputting + compact JSON to generators. Add hidden `resolve-specs` command. + type: feat + createdAt: "2026-05-20" + irVersion: 66 - version: 5.32.1 changelogEntry: - summary: | diff --git a/packages/cli/generation/local-generation/local-workspace-runner/package.json b/packages/cli/generation/local-generation/local-workspace-runner/package.json index a55085ba105b..745b2f047c52 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/package.json +++ b/packages/cli/generation/local-generation/local-workspace-runner/package.json @@ -53,6 +53,7 @@ "@fern-api/ir-generator": "workspace:*", "@fern-api/ir-migrations": "workspace:*", "@fern-api/ir-sdk": "workspace:*", + "js-yaml": "catalog:", "@fern-api/java-dynamic-snippets": "workspace:*", "@fern-api/lazy-fern-workspace": "workspace:*", "@fern-api/logger": "workspace:*", @@ -77,6 +78,7 @@ "devDependencies": { "@fern-api/configs": "workspace:*", "@types/decompress": "catalog:", + "@types/js-yaml": "catalog:", "@types/node": "catalog:", "@types/semver": "catalog:", "typescript": "catalog:", diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/GenerationRunner.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/GenerationRunner.ts index e277eb278dbd..62d812a1c267 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/GenerationRunner.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/GenerationRunner.ts @@ -3,7 +3,8 @@ import { detectInvocationSource, FernWorkspace, getOriginGitCommit, - getOriginGitCommitIsDirty + getOriginGitCommitIsDirty, + type Spec } from "@fern-api/api-workspace-commons"; import { SourceResolverImpl } from "@fern-api/cli-source-resolver"; import { generatorsYml, SNIPPET_JSON_FILENAME } from "@fern-api/configuration"; @@ -48,6 +49,12 @@ export declare namespace GenerationRunner { * emission to the remaining language generators). */ verify?: boolean; + /** + * Raw API spec files (OpenAPI, protobuf, etc.) to pre-process and mount + * into the generator container. When provided, specs are bundled, overrides + * merged, overlays applied, and the result is written as compact JSON. + */ + rawApiSpecs?: Spec[]; /** * Container runtime to use when `verify` is true. Defaults to "docker". * Ignored when `verify` is false. @@ -85,6 +92,7 @@ export class GenerationRunner { inspect, skipFernignore, skipAutogenerationIfManualExamplesExist, + rawApiSpecs, verify, verifyRunner, verifyValidatorVersion @@ -115,7 +123,8 @@ export class GenerationRunner { absolutePathToFernConfig, inspect, skipFernignore, - skipAutogenerationIfManualExamplesExist + skipAutogenerationIfManualExamplesExist, + rawApiSpecs }); interactiveTaskContext.logger.info( @@ -178,7 +187,8 @@ export class GenerationRunner { absolutePathToFernConfig, inspect, skipFernignore, - skipAutogenerationIfManualExamplesExist + skipAutogenerationIfManualExamplesExist, + rawApiSpecs }: { generatorGroup: generatorsYml.GeneratorGroup; generatorInvocation: generatorsYml.GeneratorInvocation; @@ -191,6 +201,7 @@ export class GenerationRunner { inspect: boolean; skipFernignore?: boolean; skipAutogenerationIfManualExamplesExist?: boolean; + rawApiSpecs?: Spec[]; }): Promise<{ ir: IntermediateRepresentation; generatorConfig: FernGeneratorExec.GeneratorConfig; @@ -268,7 +279,8 @@ export class GenerationRunner { runner: undefined, ai: workspace.generatorsConfiguration?.ai, absolutePathToSpecRepo: undefined, - skipFernignore + skipFernignore, + rawApiSpecs }); } } diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/__test__/rawSpecs.test.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/__test__/rawSpecs.test.ts new file mode 100644 index 000000000000..2d7a62844bec --- /dev/null +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/__test__/rawSpecs.test.ts @@ -0,0 +1,877 @@ +import { AbsoluteFilePath, RelativeFilePath } from "@fern-api/fs-utils"; +import { mkdir, readFile, rm, writeFile } from "fs/promises"; +import path from "path"; +import tmp from "tmp-promise"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { collectRawSpecs, filterSpec } from "../rawSpecs.js"; + +// biome-ignore lint/suspicious/noExplicitAny: mock context for testing +function createMockContext(): any { + return { + logger: { + // biome-ignore lint/suspicious/noEmptyBlockStatements: noop logger + debug: () => {}, + // biome-ignore lint/suspicious/noEmptyBlockStatements: noop logger + info: () => {}, + // biome-ignore lint/suspicious/noEmptyBlockStatements: noop logger + warn: () => {}, + // biome-ignore lint/suspicious/noEmptyBlockStatements: noop logger + error: () => {}, + // biome-ignore lint/suspicious/noEmptyBlockStatements: noop logger + trace: () => {}, + // biome-ignore lint/suspicious/noEmptyBlockStatements: noop logger + log: () => {} + }, + failAndThrow: () => { + throw new Error("Task failed"); + }, + // biome-ignore lint/suspicious/noEmptyBlockStatements: noop mock + failWithoutThrowing: () => {}, + isCancelled: false, + // biome-ignore lint/suspicious/noEmptyBlockStatements: noop mock + runInteractiveTask: async () => {}, + // biome-ignore lint/suspicious/noEmptyBlockStatements: noop mock + takeOverTerminal: async () => {} + }; +} + +const MINIMAL_OPENAPI = ['openapi: "3.0.0"', "info:", " title: Test", ' version: "1.0"', "paths: {}", ""].join("\n"); + +describe("collectRawSpecs", () => { + let tmpDir: tmp.DirectoryResult; + let sourceDir: string; + + beforeEach(async () => { + tmpDir = await tmp.dir({ unsafeCleanup: true }); + sourceDir = path.join(tmpDir.path, "source"); + await mkdir(path.join(sourceDir, "api"), { recursive: true }); + await mkdir(path.join(sourceDir, "overrides"), { recursive: true }); + await mkdir(path.join(sourceDir, "overlays"), { recursive: true }); + await mkdir(path.join(sourceDir, "proto", "service"), { recursive: true }); + }); + + afterEach(async () => { + await rm(tmpDir.path, { recursive: true, force: true }); + }); + + it("returns empty manifest for empty specs array", async () => { + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs).toHaveLength(0); + }); + + it("resolves a single OpenAPI spec to compact JSON", async () => { + const specFile = path.join(sourceDir, "api", "openapi.yaml"); + await writeFile(specFile, MINIMAL_OPENAPI); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(specFile), + absoluteFilepathToOverrides: undefined, + absoluteFilepathToOverlays: undefined, + source: { + type: "openapi", + file: AbsoluteFilePath.of(specFile), + relativePathToDependency: undefined + } + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs).toHaveLength(1); + expect(manifest.specs[0]?.type).toBe("openapi"); + expect(manifest.specs[0]?.specPath).toBe("/fern/specs/spec-0.json"); + expect(manifest.specs[0]?.overridePaths).toBeUndefined(); + + const content = await readFile(path.join(outputDir, "spec-0.json"), "utf-8"); + const parsed = JSON.parse(content); + expect(parsed.openapi).toBe("3.0.0"); + expect(parsed.info.title).toBe("Test"); + // Compact JSON has no newlines + expect(content).not.toContain("\n"); + }); + + it("merges overrides into the resolved OpenAPI spec", async () => { + const specFile = path.join(sourceDir, "api", "openapi.yaml"); + const overrideFile = path.join(sourceDir, "overrides", "override.yaml"); + + await writeFile(specFile, MINIMAL_OPENAPI); + await writeFile(overrideFile, 'info:\n description: "Added by override"\n'); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(specFile), + absoluteFilepathToOverrides: AbsoluteFilePath.of(overrideFile), + absoluteFilepathToOverlays: undefined, + source: { + type: "openapi", + file: AbsoluteFilePath.of(specFile), + relativePathToDependency: undefined + } + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs).toHaveLength(1); + expect(manifest.specs[0]?.overridePaths).toBeUndefined(); + + const content = await readFile(path.join(outputDir, "spec-0.json"), "utf-8"); + const parsed = JSON.parse(content); + expect(parsed.info.description).toBe("Added by override"); + expect(parsed.info.title).toBe("Test"); + }); + + it("bundles external refs into a single self-contained JSON", async () => { + const specFile = path.join(sourceDir, "api", "openapi.yaml"); + const sharedDir = path.join(sourceDir, "shared"); + const sharedModel = path.join(sharedDir, "models.yaml"); + + await mkdir(sharedDir, { recursive: true }); + await writeFile( + specFile, + [ + 'openapi: "3.0.0"', + "info:", + " title: Test", + ' version: "1.0"', + "paths:", + " /users:", + " get:", + " operationId: getUsers", + " responses:", + ' "200":', + " description: OK", + " content:", + " application/json:", + " schema:", + ' $ref: "../shared/models.yaml#/User"' + ].join("\n") + ); + await writeFile( + sharedModel, + ["User:", " type: object", " properties:", " name:", " type: string"].join("\n") + ); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(specFile), + absoluteFilepathToOverrides: undefined, + absoluteFilepathToOverlays: undefined, + source: { + type: "openapi", + file: AbsoluteFilePath.of(specFile), + relativePathToDependency: undefined + } + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs).toHaveLength(1); + + const content = await readFile(path.join(outputDir, "spec-0.json"), "utf-8"); + const parsed = JSON.parse(content); + // External $ref should be resolved/inlined + expect(JSON.stringify(parsed)).toContain("name"); + }); + + it("handles multiple OpenAPI specs with indexed filenames", async () => { + const spec1 = path.join(sourceDir, "api", "v1.yaml"); + const spec2 = path.join(sourceDir, "api", "v2.yaml"); + + await writeFile(spec1, 'openapi: "3.0.0"\ninfo:\n title: V1\n version: "1.0"\npaths: {}'); + await writeFile(spec2, 'openapi: "3.0.0"\ninfo:\n title: V2\n version: "2.0"\npaths: {}'); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(spec1), + absoluteFilepathToOverrides: undefined, + absoluteFilepathToOverlays: undefined, + source: { type: "openapi", file: AbsoluteFilePath.of(spec1), relativePathToDependency: undefined } + }, + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(spec2), + absoluteFilepathToOverrides: undefined, + absoluteFilepathToOverlays: undefined, + source: { type: "openapi", file: AbsoluteFilePath.of(spec2), relativePathToDependency: undefined } + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs).toHaveLength(2); + expect(manifest.specs[0]?.specPath).toBe("/fern/specs/spec-0.json"); + expect(manifest.specs[1]?.specPath).toBe("/fern/specs/spec-1.json"); + + const content0 = JSON.parse(await readFile(path.join(outputDir, "spec-0.json"), "utf-8")); + const content1 = JSON.parse(await readFile(path.join(outputDir, "spec-1.json"), "utf-8")); + expect(content0.info.title).toBe("V1"); + expect(content1.info.title).toBe("V2"); + }); + + it("copies protobuf directory as-is", async () => { + const protoRoot = path.join(sourceDir, "proto"); + await writeFile(path.join(protoRoot, "service", "api.proto"), 'syntax = "proto3";'); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "protobuf", + absoluteFilepathToProtobufRoot: AbsoluteFilePath.of(protoRoot), + absoluteFilepathToProtobufTarget: undefined, + absoluteFilepathToOverrides: undefined, + relativeFilepathToProtobufRoot: RelativeFilePath.of("proto"), + generateLocally: false, + fromOpenAPI: false, + dependencies: [] + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs).toHaveLength(1); + expect(manifest.specs[0]?.type).toBe("protobuf"); + expect(manifest.specs[0]?.specPath).toBe("/fern/specs/proto-0"); + + const copiedProto = await readFile(path.join(outputDir, "proto-0", "service", "api.proto"), "utf-8"); + expect(copiedProto).toBe('syntax = "proto3";'); + }); + + it("copies protobuf directory with override files", async () => { + const protoRoot = path.join(sourceDir, "proto"); + const overrideFile = path.join(sourceDir, "overrides", "override.yaml"); + + await writeFile(path.join(protoRoot, "service", "api.proto"), 'syntax = "proto3";'); + await writeFile(overrideFile, "override: true"); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "protobuf", + absoluteFilepathToProtobufRoot: AbsoluteFilePath.of(protoRoot), + absoluteFilepathToProtobufTarget: undefined, + absoluteFilepathToOverrides: AbsoluteFilePath.of(overrideFile), + relativeFilepathToProtobufRoot: RelativeFilePath.of("proto"), + generateLocally: false, + fromOpenAPI: false, + dependencies: [] + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs[0]?.overridePaths).toHaveLength(1); + expect(manifest.specs[0]?.overridePaths?.[0]).toContain("proto-0-override-0"); + }); + + it("copies GraphQL spec as-is", async () => { + const graphqlFile = path.join(sourceDir, "api", "schema.graphql"); + await writeFile(graphqlFile, "type Query { hello: String }"); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "graphql", + absoluteFilepath: AbsoluteFilePath.of(graphqlFile), + absoluteFilepathToOverrides: undefined + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs).toHaveLength(1); + expect(manifest.specs[0]?.type).toBe("graphql"); + expect(manifest.specs[0]?.specPath).toBe("/fern/specs/spec-0.graphql"); + + const content = await readFile(path.join(outputDir, "spec-0.graphql"), "utf-8"); + expect(content).toBe("type Query { hello: String }"); + }); + + it("resolves OpenRPC spec with overrides to compact JSON", async () => { + const specFile = path.join(sourceDir, "api", "openrpc.json"); + const overrideFile = path.join(sourceDir, "overrides", "override.json"); + + await writeFile(specFile, JSON.stringify({ openrpc: "1.0.0", info: { title: "Test", version: "1.0" } })); + await writeFile(overrideFile, JSON.stringify({ info: { description: "Added by override" } })); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "openrpc", + absoluteFilepath: AbsoluteFilePath.of(specFile), + absoluteFilepathToOverrides: AbsoluteFilePath.of(overrideFile) + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs).toHaveLength(1); + expect(manifest.specs[0]?.type).toBe("openrpc"); + expect(manifest.specs[0]?.overridePaths).toBeUndefined(); + + const content = await readFile(path.join(outputDir, "spec-0.json"), "utf-8"); + const parsed = JSON.parse(content); + expect(parsed.openrpc).toBe("1.0.0"); + expect(parsed.info.description).toBe("Added by override"); + }); + + it("handles mixed spec types (OpenAPI + protobuf + GraphQL)", async () => { + const specFile = path.join(sourceDir, "api", "openapi.yaml"); + const protoRoot = path.join(sourceDir, "proto"); + const graphqlFile = path.join(sourceDir, "api", "schema.graphql"); + + await writeFile(specFile, MINIMAL_OPENAPI); + await writeFile(path.join(protoRoot, "service", "api.proto"), 'syntax = "proto3";'); + await writeFile(graphqlFile, "type Query { hello: String }"); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(specFile), + absoluteFilepathToOverrides: undefined, + absoluteFilepathToOverlays: undefined, + source: { + type: "openapi", + file: AbsoluteFilePath.of(specFile), + relativePathToDependency: undefined + } + }, + { + type: "protobuf", + absoluteFilepathToProtobufRoot: AbsoluteFilePath.of(protoRoot), + absoluteFilepathToProtobufTarget: undefined, + absoluteFilepathToOverrides: undefined, + relativeFilepathToProtobufRoot: RelativeFilePath.of("proto"), + generateLocally: false, + fromOpenAPI: false, + dependencies: [] + }, + { + type: "graphql", + absoluteFilepath: AbsoluteFilePath.of(graphqlFile), + absoluteFilepathToOverrides: undefined + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs).toHaveLength(3); + expect(manifest.specs[0]?.type).toBe("openapi"); + expect(manifest.specs[0]?.specPath).toBe("/fern/specs/spec-0.json"); + expect(manifest.specs[1]?.type).toBe("protobuf"); + expect(manifest.specs[1]?.specPath).toBe("/fern/specs/proto-1"); + expect(manifest.specs[2]?.type).toBe("graphql"); + expect(manifest.specs[2]?.specPath).toBe("/fern/specs/spec-2.graphql"); + }); + + it("uses container paths in manifest entries", async () => { + const specFile = path.join(sourceDir, "api", "openapi.yaml"); + await writeFile(specFile, MINIMAL_OPENAPI); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(specFile), + absoluteFilepathToOverrides: undefined, + absoluteFilepathToOverlays: undefined, + source: { + type: "openapi", + file: AbsoluteFilePath.of(specFile), + relativePathToDependency: undefined + } + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs[0]?.specPath).toMatch(/^\/fern\/specs\//); + }); + + it("merges array overrides sequentially into OpenAPI spec", async () => { + const specFile = path.join(sourceDir, "api", "openapi.yaml"); + const override1 = path.join(sourceDir, "overrides", "override1.yaml"); + const override2 = path.join(sourceDir, "overrides", "override2.yaml"); + + await writeFile(specFile, MINIMAL_OPENAPI); + await writeFile(override1, 'info:\n description: "From override 1"'); + await writeFile(override2, 'info:\n x-custom: "From override 2"'); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(specFile), + absoluteFilepathToOverrides: [AbsoluteFilePath.of(override1), AbsoluteFilePath.of(override2)], + absoluteFilepathToOverlays: undefined, + source: { + type: "openapi", + file: AbsoluteFilePath.of(specFile), + relativePathToDependency: undefined + } + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs[0]?.overridePaths).toBeUndefined(); + + const content = await readFile(path.join(outputDir, "spec-0.json"), "utf-8"); + const parsed = JSON.parse(content); + expect(parsed.info.description).toBe("From override 1"); + expect(parsed.info["x-custom"]).toBe("From override 2"); + }); + + it("does not copy raw files - only outputs resolved JSON", async () => { + const specFile = path.join(sourceDir, "api", "openapi.yaml"); + await writeFile(specFile, MINIMAL_OPENAPI); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + await collectRawSpecs({ + specs: [ + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(specFile), + absoluteFilepathToOverrides: undefined, + absoluteFilepathToOverlays: undefined, + source: { + type: "openapi", + file: AbsoluteFilePath.of(specFile), + relativePathToDependency: undefined + } + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + const resolvedContent = await readFile(path.join(outputDir, "spec-0.json"), "utf-8"); + expect(resolvedContent).toBeTruthy(); + + await expect(readFile(path.join(outputDir, "openapi.yaml"), "utf-8")).rejects.toThrow(); + await expect(readFile(path.join(outputDir, "api", "openapi.yaml"), "utf-8")).rejects.toThrow(); + }); + + it("GraphQL spec with overrides keeps overrides as separate files", async () => { + const graphqlFile = path.join(sourceDir, "api", "schema.graphql"); + const overrideFile = path.join(sourceDir, "overrides", "override.yaml"); + + await writeFile(graphqlFile, "type Query { hello: String }"); + await writeFile(overrideFile, "override: true"); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "graphql", + absoluteFilepath: AbsoluteFilePath.of(graphqlFile), + absoluteFilepathToOverrides: AbsoluteFilePath.of(overrideFile) + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + expect(manifest.specs[0]?.overridePaths).toHaveLength(1); + expect(manifest.specs[0]?.overridePaths?.[0]).toContain("graphql-0-override-0"); + }); + + it("filters out x-fern-ignore operations from resolved OpenAPI spec", async () => { + const specFile = path.join(sourceDir, "api", "openapi.yaml"); + await writeFile( + specFile, + [ + 'openapi: "3.0.0"', + "info:", + " title: Test", + ' version: "1.0"', + "paths:", + " /users:", + " get:", + " operationId: getUsers", + " responses:", + ' "200":', + " description: OK", + " post:", + " operationId: createUser", + " x-fern-ignore: true", + " responses:", + ' "200":', + " description: OK", + " /internal:", + " get:", + " operationId: internalEndpoint", + " x-fern-ignore: true", + " responses:", + ' "200":', + " description: OK" + ].join("\n") + ); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(specFile), + absoluteFilepathToOverrides: undefined, + absoluteFilepathToOverlays: undefined, + source: { + type: "openapi", + file: AbsoluteFilePath.of(specFile), + relativePathToDependency: undefined + } + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + context: createMockContext() + }); + + const content = JSON.parse(await readFile(path.join(outputDir, "spec-0.json"), "utf-8")); + expect(content.paths["/users"]).toBeDefined(); + expect(content.paths["/users"].get).toBeDefined(); + expect(content.paths["/users"].post).toBeUndefined(); + expect(content.paths["/internal"]).toBeUndefined(); + + expect(manifest.specs).toHaveLength(1); + }); + + it("filters operations by x-fern-audiences when audiences are configured", async () => { + const specFile = path.join(sourceDir, "api", "openapi.yaml"); + await writeFile( + specFile, + [ + 'openapi: "3.0.0"', + "info:", + " title: Test", + ' version: "1.0"', + "paths:", + " /public:", + " get:", + " operationId: publicEndpoint", + " x-fern-audiences:", + " - external", + " responses:", + ' "200":', + " description: OK", + " /internal:", + " get:", + " operationId: internalEndpoint", + " x-fern-audiences:", + " - internal", + " responses:", + ' "200":', + " description: OK", + " /untagged:", + " get:", + " operationId: untaggedEndpoint", + " responses:", + ' "200":', + " description: OK" + ].join("\n") + ); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(specFile), + absoluteFilepathToOverrides: undefined, + absoluteFilepathToOverlays: undefined, + source: { + type: "openapi", + file: AbsoluteFilePath.of(specFile), + relativePathToDependency: undefined + } + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + audiences: { type: "select", audiences: ["external"] }, + context: createMockContext() + }); + + const content = JSON.parse(await readFile(path.join(outputDir, "spec-0.json"), "utf-8")); + expect(content.paths["/public"]).toBeDefined(); + expect(content.paths["/public"].get).toBeDefined(); + expect(content.paths["/internal"]).toBeUndefined(); + expect(content.paths["/untagged"]).toBeDefined(); + expect(content.paths["/untagged"].get).toBeDefined(); + + expect(manifest.specs).toHaveLength(1); + }); + + it("keeps all operations when audiences type is 'all'", async () => { + const specFile = path.join(sourceDir, "api", "openapi.yaml"); + await writeFile( + specFile, + [ + 'openapi: "3.0.0"', + "info:", + " title: Test", + ' version: "1.0"', + "paths:", + " /public:", + " get:", + " operationId: publicEndpoint", + " x-fern-audiences:", + " - external", + " responses:", + ' "200":', + " description: OK", + " /internal:", + " get:", + " operationId: internalEndpoint", + " x-fern-audiences:", + " - internal", + " responses:", + ' "200":', + " description: OK" + ].join("\n") + ); + + const outputDir = path.join(tmpDir.path, "output"); + await mkdir(outputDir, { recursive: true }); + + const manifest = await collectRawSpecs({ + specs: [ + { + type: "openapi", + absoluteFilepath: AbsoluteFilePath.of(specFile), + absoluteFilepathToOverrides: undefined, + absoluteFilepathToOverlays: undefined, + source: { + type: "openapi", + file: AbsoluteFilePath.of(specFile), + relativePathToDependency: undefined + } + } + ], + hostOutputDir: AbsoluteFilePath.of(outputDir), + containerBaseDir: "/fern/specs", + audiences: { type: "all" }, + context: createMockContext() + }); + + const content = JSON.parse(await readFile(path.join(outputDir, "spec-0.json"), "utf-8")); + expect(content.paths["/public"]).toBeDefined(); + expect(content.paths["/internal"]).toBeDefined(); + }); +}); + +describe("filterSpec", () => { + it("returns spec unchanged when no audiences and no ignored operations", () => { + const spec = { + openapi: "3.0.0", + paths: { + "/users": { + get: { operationId: "getUsers" }, + post: { operationId: "createUser" } + } + } + }; + const result = filterSpec(spec); + expect(result.paths).toEqual(spec.paths); + }); + + it("removes operations with x-fern-ignore: true", () => { + const spec = { + openapi: "3.0.0", + paths: { + "/users": { + get: { operationId: "getUsers" }, + post: { operationId: "createUser", "x-fern-ignore": true } + }, + "/admin": { + get: { operationId: "adminGet", "x-fern-ignore": true } + } + } + }; + const result = filterSpec(spec); + const paths = result.paths as Record> | undefined; + expect(paths?.["/users"]?.get).toBeDefined(); + expect(paths?.["/users"]?.post).toBeUndefined(); + expect(paths?.["/admin"]).toBeUndefined(); + }); + + it("filters by audiences when SelectAudiences is provided", () => { + const spec = { + openapi: "3.0.0", + paths: { + "/public": { + get: { operationId: "publicGet", "x-fern-audiences": ["external"] } + }, + "/internal": { + get: { operationId: "internalGet", "x-fern-audiences": ["internal"] } + }, + "/both": { + get: { operationId: "bothGet", "x-fern-audiences": ["external", "internal"] } + }, + "/untagged": { + get: { operationId: "untaggedGet" } + } + } + }; + const result = filterSpec(spec, { type: "select", audiences: ["external"] }); + const paths = result.paths as Record> | undefined; + expect(paths?.["/public"]).toBeDefined(); + expect(paths?.["/internal"]).toBeUndefined(); + expect(paths?.["/both"]).toBeDefined(); + expect(paths?.["/untagged"]).toBeDefined(); + }); + + it("combines x-fern-ignore and audience filtering", () => { + const spec = { + openapi: "3.0.0", + paths: { + "/endpoint": { + get: { operationId: "kept", "x-fern-audiences": ["external"] }, + post: { operationId: "ignored", "x-fern-ignore": true, "x-fern-audiences": ["external"] }, + put: { operationId: "wrongAudience", "x-fern-audiences": ["internal"] }, + delete: { operationId: "noAudience" } + } + } + }; + const result = filterSpec(spec, { type: "select", audiences: ["external"] }); + const paths = result.paths as Record> | undefined; + expect(paths?.["/endpoint"]?.get).toBeDefined(); + expect(paths?.["/endpoint"]?.post).toBeUndefined(); + expect(paths?.["/endpoint"]?.put).toBeUndefined(); + expect(paths?.["/endpoint"]?.delete).toBeDefined(); + }); + + it("preserves non-operation path-level properties", () => { + const spec = { + openapi: "3.0.0", + paths: { + "/users": { + parameters: [{ name: "id", in: "path" }], + get: { operationId: "getUsers" }, + post: { operationId: "createUser", "x-fern-ignore": true } + } + } + }; + const result = filterSpec(spec); + const paths = result.paths as Record> | undefined; + expect(paths?.["/users"]?.parameters).toBeDefined(); + expect(paths?.["/users"]?.get).toBeDefined(); + expect(paths?.["/users"]?.post).toBeUndefined(); + }); + + it("returns spec unchanged when paths is missing", () => { + const spec = { openapi: "3.0.0", info: { title: "Test" } }; + const result = filterSpec(spec); + expect(result).toEqual(spec); + }); + + it("handles empty paths object", () => { + const spec = { openapi: "3.0.0", paths: {} }; + const result = filterSpec(spec); + expect(result.paths).toEqual({}); + }); + + it("handles x-fern-audiences as single string value", () => { + const spec = { + openapi: "3.0.0", + paths: { + "/endpoint": { + get: { operationId: "getEndpoint", "x-fern-audiences": "external" } + } + } + }; + const result = filterSpec(spec, { type: "select", audiences: ["external"] }); + const paths = result.paths as Record> | undefined; + expect(paths?.["/endpoint"]?.get).toBeDefined(); + + const result2 = filterSpec(spec, { type: "select", audiences: ["internal"] }); + const paths2 = result2.paths as Record> | undefined; + expect(paths2?.["/endpoint"]).toBeUndefined(); + }); +}); diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/constants.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/constants.ts index 4fb7d2b4395b..dedf5848cb87 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/constants.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/constants.ts @@ -8,6 +8,8 @@ export const SNIPPET_FILENAME = "snippet.json"; export const SNIPPET_TEMPLATES_FILENAME = "snippet-templates.json"; export const GENERATORS_DIRECTORY_NAME = "generators"; export const SOURCES_DIRECTORY_NAME = "sources"; +export const SPECS_DIRECTORY_NAME = "specs"; +export const SPECS_MANIFEST_FILENAME = "specs-manifest.json"; export const DOCKER_CODEGEN_OUTPUT_DIRECTORY = path.join(DOCKER_FERN_DIRECTORY, CODEGEN_OUTPUT_DIRECTORY_NAME); export const DOCKER_GENERATOR_CONFIG_PATH = path.join(DOCKER_FERN_DIRECTORY, GENERATOR_CONFIG_FILENAME); @@ -26,4 +28,17 @@ export const CONTAINER_PATH_TO_SNIPPET_TEMPLATES = DOCKER_PATH_TO_SNIPPET_TEMPLA export const CONTAINER_GENERATORS_DIRECTORY = DOCKER_GENERATORS_DIRECTORY; export const CONTAINER_SOURCES_DIRECTORY = DOCKER_SOURCES_DIRECTORY; +export const DOCKER_SPECS_DIRECTORY = path.join(DOCKER_FERN_DIRECTORY, SPECS_DIRECTORY_NAME); +export const CONTAINER_SPECS_DIRECTORY = DOCKER_SPECS_DIRECTORY; + export const DEFAULT_NODE_DEBUG_PORT = "9229"; + +/** + * Generators that receive pre-processed raw API spec files mounted into their + * Docker container. Add new generator names here as they opt in. + */ +const GENERATORS_WANTING_SPECS: ReadonlySet = new Set(["fernapi/fern-cli"]); + +export function generatorWantsSpecs(generatorName: string): boolean { + return GENERATORS_WANTING_SPECS.has(generatorName); +} diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/index.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/index.ts index b28749b81fa0..2125d675b42a 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/index.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/index.ts @@ -1,5 +1,6 @@ export * from "./ContainerExecutionEnvironment.js"; export { ContainerExecutionEnvironment } from "./ContainerExecutionEnvironment.js"; +export { generatorWantsSpecs, SPECS_MANIFEST_FILENAME } from "./constants.js"; export * from "./DockerExecutionEnvironment.js"; export { DockerExecutionEnvironment } from "./DockerExecutionEnvironment.js"; export * from "./ExecutionEnvironment.js"; @@ -11,6 +12,8 @@ export * from "./NativeExecutionEnvironment.js"; export { NativeExecutionEnvironment } from "./NativeExecutionEnvironment.js"; export * from "./ReusableContainerExecutionEnvironment.js"; export { ReusableContainerExecutionEnvironment } from "./ReusableContainerExecutionEnvironment.js"; +export type { RawSpecsManifest, RawSpecsManifestEntry } from "./rawSpecs.js"; +export { collectRawSpecs, filterSpec } from "./rawSpecs.js"; export { runContainerizedGenerationForSeed, runNativeGenerationForSeed, diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/rawSpecs.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/rawSpecs.ts new file mode 100644 index 000000000000..418794b0d6e8 --- /dev/null +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/rawSpecs.ts @@ -0,0 +1,401 @@ +import { + type GraphQLSpec, + type OpenAPISpec, + type OpenRPCSpec, + type ProtobufSpec, + Spec +} from "@fern-api/api-workspace-commons"; +import { type Audiences } from "@fern-api/configuration"; +import { assertNever, mergeWithOverrides as coreMergeWithOverrides } from "@fern-api/core-utils"; +import { AbsoluteFilePath } from "@fern-api/fs-utils"; +import { loadAsyncAPI, loadOpenAPI } from "@fern-api/lazy-fern-workspace"; +import { TaskContext } from "@fern-api/task-context"; +import { copyFile, cp, readFile, writeFile } from "fs/promises"; +import yaml from "js-yaml"; +import path from "path"; + +export interface RawSpecsManifestEntry { + type: "openapi" | "asyncapi" | "protobuf" | "openrpc" | "graphql"; + specPath: string; + overridePaths?: string[]; +} + +export interface RawSpecsManifest { + specs: RawSpecsManifestEntry[]; +} + +/** + * Pre-processes API specs by bundling external $refs, merging overrides, and + * applying overlays, then writes each resolved spec as compact JSON (no + * whitespace). Schemas referenced from multiple external files are + * deduplicated into a single `#/components/schemas/` entry by Redocly's + * bundler. + * + * Protobuf and GraphQL specs are copied as-is since they cannot be + * meaningfully bundled. + * + * Returns a manifest describing the container paths for each spec. + */ +export async function collectRawSpecs({ + specs, + hostOutputDir, + containerBaseDir, + context, + audiences +}: { + specs: Spec[]; + hostOutputDir: AbsoluteFilePath; + containerBaseDir: string; + context: TaskContext; + audiences?: Audiences; +}): Promise { + const manifest: RawSpecsManifest = { specs: [] }; + if (specs.length === 0) { + return manifest; + } + + for (const [i, spec] of specs.entries()) { + const entry = await resolveAndWriteSpec({ + spec, + hostOutputDir, + containerBaseDir, + context, + index: i, + audiences + }); + manifest.specs.push(entry); + } + + context.logger.debug(`Resolved ${manifest.specs.length} spec(s) to ${hostOutputDir}`); + return manifest; +} + +async function resolveAndWriteSpec({ + spec, + hostOutputDir, + containerBaseDir, + context, + index, + audiences +}: { + spec: Spec; + hostOutputDir: string; + containerBaseDir: string; + context: TaskContext; + index: number; + audiences?: Audiences; +}): Promise { + switch (spec.type) { + case "openapi": + return resolveOpenAPIOrAsyncAPI({ spec, hostOutputDir, containerBaseDir, context, index, audiences }); + case "openrpc": + return resolveOpenRPC({ spec, hostOutputDir, containerBaseDir, context, index }); + case "protobuf": + return copyProtobuf({ spec, hostOutputDir, containerBaseDir, index }); + case "graphql": + return copyGraphQL({ spec, hostOutputDir, containerBaseDir, index }); + default: + assertNever(spec); + } +} + +/** + * Bundles an OpenAPI or AsyncAPI spec: resolves all external $refs, merges + * overrides, applies overlays, and writes the result as compact JSON. + */ +async function resolveOpenAPIOrAsyncAPI({ + spec, + hostOutputDir, + containerBaseDir, + context, + index, + audiences +}: { + spec: OpenAPISpec; + hostOutputDir: string; + containerBaseDir: string; + context: TaskContext; + index: number; + audiences?: Audiences; +}): Promise { + const isAsync = await isAsyncAPISpec(spec.absoluteFilepath); + const filename = `spec-${index}.json`; + + let resolved: object; + if (isAsync) { + resolved = await loadAsyncAPI({ + context, + absoluteFilePath: spec.absoluteFilepath, + absoluteFilePathToOverrides: spec.absoluteFilepathToOverrides + }); + } else { + resolved = await loadOpenAPI({ + context, + absolutePathToOpenAPI: spec.absoluteFilepath, + absolutePathToOpenAPIOverrides: spec.absoluteFilepathToOverrides, + absolutePathToOpenAPIOverlays: spec.absoluteFilepathToOverlays + }); + } + + resolved = filterSpec(resolved as Record, audiences); + + await writeFile(path.join(hostOutputDir, filename), JSON.stringify(resolved)); + context.logger.debug(`Resolved ${isAsync ? "AsyncAPI" : "OpenAPI"} spec ${spec.absoluteFilepath} -> ${filename}`); + + return { + type: isAsync ? "asyncapi" : "openapi", + specPath: toContainerPath(filename, containerBaseDir) + }; +} + +/** + * Resolves an OpenRPC spec by reading, parsing, and merging overrides. + * Writes the result as compact JSON. + */ +async function resolveOpenRPC({ + spec, + hostOutputDir, + containerBaseDir, + context, + index +}: { + spec: OpenRPCSpec; + hostOutputDir: string; + containerBaseDir: string; + context: TaskContext; + index: number; +}): Promise { + const filename = `spec-${index}.json`; + const rawContent = await readFile(spec.absoluteFilepath, "utf-8"); + + let parsed: object; + try { + parsed = JSON.parse(rawContent); + } catch { + parsed = yaml.load(rawContent) as object; + } + + let result = parsed; + const overrides = normalizeOverrides(spec.absoluteFilepathToOverrides); + for (const overridePath of overrides) { + const overrideContent = await readFile(overridePath, "utf-8"); + let overrideParsed: object; + try { + overrideParsed = JSON.parse(overrideContent); + } catch { + overrideParsed = yaml.load(overrideContent) as object; + } + result = coreMergeWithOverrides({ + data: result as Record, + overrides: overrideParsed + }); + } + + await writeFile(path.join(hostOutputDir, filename), JSON.stringify(result)); + context.logger.debug(`Resolved OpenRPC spec ${spec.absoluteFilepath} -> ${filename}`); + + return { + type: "openrpc", + specPath: toContainerPath(filename, containerBaseDir) + }; +} + +/** + * Copies a protobuf root directory and any override files that cannot be + * pre-merged (they apply to the OpenAPI generated from protobuf, not to the + * .proto files themselves). + */ +async function copyProtobuf({ + spec, + hostOutputDir, + containerBaseDir, + index +}: { + spec: ProtobufSpec; + hostOutputDir: string; + containerBaseDir: string; + index: number; +}): Promise { + const dirName = `proto-${index}`; + const destDir = path.join(hostOutputDir, dirName); + await cp(spec.absoluteFilepathToProtobufRoot, destDir, { recursive: true }); + + const entry: RawSpecsManifestEntry = { + type: "protobuf", + specPath: toContainerPath(dirName, containerBaseDir) + }; + + const overrides = normalizeOverrides(spec.absoluteFilepathToOverrides); + if (overrides.length > 0) { + entry.overridePaths = []; + for (const [i, override] of overrides.entries()) { + const overrideName = `proto-${index}-override-${i}${path.extname(override)}`; + await copyFile(override, path.join(hostOutputDir, overrideName)); + entry.overridePaths.push(toContainerPath(overrideName, containerBaseDir)); + } + } + + return entry; +} + +/** + * Copies a GraphQL schema file and any override files. GraphQL SDL cannot be + * meaningfully merged with JSON/YAML overrides. + */ +async function copyGraphQL({ + spec, + hostOutputDir, + containerBaseDir, + index +}: { + spec: GraphQLSpec; + hostOutputDir: string; + containerBaseDir: string; + index: number; +}): Promise { + const ext = path.extname(spec.absoluteFilepath) || ".graphql"; + const filename = `spec-${index}${ext}`; + await copyFile(spec.absoluteFilepath, path.join(hostOutputDir, filename)); + + const entry: RawSpecsManifestEntry = { + type: "graphql", + specPath: toContainerPath(filename, containerBaseDir) + }; + + const overrides = normalizeOverrides(spec.absoluteFilepathToOverrides); + if (overrides.length > 0) { + entry.overridePaths = []; + for (const [i, override] of overrides.entries()) { + const overrideName = `graphql-${index}-override-${i}${path.extname(override)}`; + await copyFile(override, path.join(hostOutputDir, overrideName)); + entry.overridePaths.push(toContainerPath(overrideName, containerBaseDir)); + } + } + + return entry; +} + +async function isAsyncAPISpec(filepath: string): Promise { + try { + const content = await readFile(filepath, "utf-8"); + return content.includes("asyncapi"); + } catch { + return false; + } +} + +function normalizeOverrides(overrides: AbsoluteFilePath | AbsoluteFilePath[] | undefined): string[] { + if (overrides == null) { + return []; + } + return Array.isArray(overrides) ? overrides : [overrides]; +} + +function toContainerPath(relativePath: string, containerBaseDir: string): string { + return path.posix.join(containerBaseDir, relativePath.split(path.sep).join(path.posix.sep)); +} + +const HTTP_METHODS = new Set(["get", "post", "put", "patch", "delete", "head", "options", "trace"]); + +/** + * Filters a resolved OpenAPI/AsyncAPI spec by removing operations marked with + * `x-fern-ignore: true` and operations whose `x-fern-audiences` do not overlap + * with the configured audiences. Operations without `x-fern-audiences` are kept + * regardless of audience configuration (they are not restricted). + * + * Paths with no remaining operations after filtering are removed entirely. + */ +export function filterSpec(spec: Record, audiences?: Audiences): Record { + if (audiences == null || audiences.type === "all") { + return filterIgnoredOperations(spec); + } + + const selectedAudiences = new Set(audiences.audiences); + return filterOperations(spec, selectedAudiences); +} + +function filterIgnoredOperations(spec: Record): Record { + const paths = spec.paths as Record> | undefined; + if (paths == null) { + return spec; + } + + const filteredPaths: Record> = {}; + for (const [pathKey, pathItem] of Object.entries(paths)) { + if (pathItem == null || typeof pathItem !== "object") { + continue; + } + const filteredPathItem: Record = {}; + for (const [key, value] of Object.entries(pathItem)) { + if (HTTP_METHODS.has(key.toLowerCase()) && isIgnored(value)) { + continue; + } + filteredPathItem[key] = value; + } + if (hasOperations(filteredPathItem)) { + filteredPaths[pathKey] = filteredPathItem; + } + } + + return { ...spec, paths: filteredPaths }; +} + +function filterOperations(spec: Record, selectedAudiences: Set): Record { + const paths = spec.paths as Record> | undefined; + if (paths == null) { + return spec; + } + + const filteredPaths: Record> = {}; + for (const [pathKey, pathItem] of Object.entries(paths)) { + if (pathItem == null || typeof pathItem !== "object") { + continue; + } + const filteredPathItem: Record = {}; + for (const [key, value] of Object.entries(pathItem)) { + if (!HTTP_METHODS.has(key.toLowerCase())) { + filteredPathItem[key] = value; + continue; + } + if (isIgnored(value)) { + continue; + } + if (!matchesAudiences(value, selectedAudiences)) { + continue; + } + filteredPathItem[key] = value; + } + if (hasOperations(filteredPathItem)) { + filteredPaths[pathKey] = filteredPathItem; + } + } + + return { ...spec, paths: filteredPaths }; +} + +function isIgnored(operation: unknown): boolean { + if (operation == null || typeof operation !== "object") { + return false; + } + const op = operation as Record; + return op["x-fern-ignore"] === true; +} + +function matchesAudiences(operation: unknown, selectedAudiences: Set): boolean { + if (operation == null || typeof operation !== "object") { + return true; + } + const op = operation as Record; + const opAudiences = op["x-fern-audiences"]; + if (opAudiences == null) { + return true; + } + if (!Array.isArray(opAudiences)) { + return typeof opAudiences === "string" && selectedAudiences.has(opAudiences); + } + return opAudiences.some((a) => typeof a === "string" && selectedAudiences.has(a)); +} + +function hasOperations(pathItem: Record): boolean { + return Object.keys(pathItem).some((key) => HTTP_METHODS.has(key.toLowerCase())); +} diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts index 18c242ed07df..971e7f18c93f 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts @@ -1,3 +1,4 @@ +import { Spec } from "@fern-api/api-workspace-commons"; import { Audiences, generatorsYml, SNIPPET_TEMPLATES_JSON_FILENAME } from "@fern-api/configuration"; import { ContainerRunner } from "@fern-api/core-utils"; import { AbsoluteFilePath, streamObjectToFile } from "@fern-api/fs-utils"; @@ -24,13 +25,17 @@ import { CONTAINER_PATH_TO_SNIPPET, CONTAINER_PATH_TO_SNIPPET_TEMPLATES, CONTAINER_SOURCES_DIRECTORY, + CONTAINER_SPECS_DIRECTORY, GENERATOR_CONFIG_FILENAME, - IR_FILENAME + IR_FILENAME, + SPECS_DIRECTORY_NAME, + SPECS_MANIFEST_FILENAME } from "./constants.js"; -import { ExecutionEnvironment } from "./ExecutionEnvironment.js"; +import { ExecutionEnvironment, SourceMount } from "./ExecutionEnvironment.js"; import { getGeneratorConfig, getLicensePathFromConfig } from "./getGeneratorConfig.js"; import { getIntermediateRepresentation } from "./getIntermediateRepresentation.js"; import { LocalTaskHandler } from "./LocalTaskHandler.js"; +import { collectRawSpecs } from "./rawSpecs.js"; export interface GeneratorRunResponse { ir: IntermediateRepresentation; @@ -84,7 +89,8 @@ export async function writeFilesToDiskAndRunGenerator({ autoVersioningCache, absolutePathToSpecRepo, skipFernignore, - disableTelemetry + disableTelemetry, + rawApiSpecs }: { organization: string; absolutePathToFernConfig: AbsoluteFilePath | undefined; @@ -115,6 +121,7 @@ export async function writeFilesToDiskAndRunGenerator({ absolutePathToSpecRepo: AbsoluteFilePath | undefined; skipFernignore?: boolean; disableTelemetry?: boolean; + rawApiSpecs?: Spec[]; }): Promise<{ ir: IntermediateRepresentation; generatorConfig: FernGeneratorExec.GeneratorConfig; @@ -235,7 +242,7 @@ export async function writeFilesToDiskAndRunGenerator({ // Extract LICENSE file path for Docker mounting const absolutePathToLicenseFile = extractLicenseFilePath(generatorInvocation, absolutePathToFernConfig); - const sourceMounts = workspace + const sourceMounts: SourceMount[] = workspace .getSources() .filter((source): source is IdentifiableSource & { type: "protobuf" } => source.type === "protobuf") .map((source) => ({ @@ -243,6 +250,32 @@ export async function writeFilesToDiskAndRunGenerator({ containerPath: `${CONTAINER_SOURCES_DIRECTORY}/${source.id}` })); + // Pre-process and mount raw API spec files when provided. OpenAPI/AsyncAPI specs are + // bundled (all $refs resolved), overrides merged, and overlays applied before mounting. + // Protobuf and GraphQL specs are copied as-is. + if (rawApiSpecs != null && rawApiSpecs.length > 0) { + const rawSpecsDir = join(workspaceTempDir.path, SPECS_DIRECTORY_NAME); + await mkdir(rawSpecsDir, { recursive: true }); + + const containerSpecsDir = CONTAINER_SPECS_DIRECTORY; + + const manifest = await collectRawSpecs({ + specs: rawApiSpecs, + hostOutputDir: AbsoluteFilePath.of(rawSpecsDir), + containerBaseDir: containerSpecsDir, + context, + audiences + }); + + await writeFile(join(rawSpecsDir, SPECS_MANIFEST_FILENAME), JSON.stringify(manifest, undefined, 4)); + context.logger.debug(`Wrote raw specs manifest with ${manifest.specs.length} spec(s) to ${rawSpecsDir}`); + + sourceMounts.push({ + hostPath: AbsoluteFilePath.of(rawSpecsDir), + containerPath: CONTAINER_SPECS_DIRECTORY + }); + } + await environment.execute({ generatorName: generatorInvocation.name, irPath: absolutePathToIr, diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts index a238c9d1cc10..ae6032893ded 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts @@ -37,6 +37,7 @@ import * as fs from "fs/promises"; import os from "os"; import path from "path"; import tmp from "tmp-promise"; +import { generatorWantsSpecs } from "./constants.js"; import { getGeneratorOutputSubfolder } from "./getGeneratorOutputSubfolder.js"; import { writeFilesToDiskAndRunGenerator } from "./runGenerator.js"; @@ -394,7 +395,11 @@ export async function runLocalGenerationForWorkspace({ autoVersioningCache, absolutePathToSpecRepo: dirname(workspace.absoluteFilePath), skipFernignore, - disableTelemetry + disableTelemetry, + rawApiSpecs: + workspace instanceof OSSWorkspace && generatorWantsSpecs(generatorInvocation.name) + ? workspace.allSpecs + : undefined }); interactiveTaskContext.logger.info(chalk.green("Wrote files to " + absolutePathToLocalOutput)); diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/customDomainValidation.test.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/customDomainValidation.test.ts new file mode 100644 index 000000000000..5d78f4efa10c --- /dev/null +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/customDomainValidation.test.ts @@ -0,0 +1,114 @@ +import { CliError, TaskContext } from "@fern-api/task-context"; +import { describe, expect, it, vi } from "vitest"; + +import { getBasepath, stripCustomDomainProtocol, validateBasepathAlignment } from "../customDomainValidation.js"; + +interface FakeContext { + context: TaskContext; + failAndThrow: ReturnType; +} + +function createFakeContext(): FakeContext { + const failAndThrow = vi.fn((message?: string) => { + throw new Error(message ?? "failAndThrow"); + }); + // Test helper: TaskContext has a wide surface; this validation only touches `failAndThrow`. + // The cast is the CLAUDE.md-documented test mock exception. + const context = { + failAndThrow + } as unknown as TaskContext; + return { context, failAndThrow }; +} + +describe("stripCustomDomainProtocol", () => { + it("strips https:// prefix", () => { + expect(stripCustomDomainProtocol("https://docs.example.com")).toBe("docs.example.com"); + }); + + it("strips http:// prefix", () => { + expect(stripCustomDomainProtocol("http://docs.example.com/path")).toBe("docs.example.com/path"); + }); + + it("leaves bare hostnames untouched", () => { + expect(stripCustomDomainProtocol("docs.example.com")).toBe("docs.example.com"); + }); + + it("does not strip protocol-like substrings that are not prefixes", () => { + expect(stripCustomDomainProtocol("docs.example.com/https://x")).toBe("docs.example.com/https://x"); + }); +}); + +describe("getBasepath", () => { + it("returns / for a bare hostname", () => { + expect(getBasepath("docs.example.com")).toBe("/"); + }); + + it("returns the basepath for a hostname with a path", () => { + expect(getBasepath("docs.example.com/docs")).toBe("/docs"); + }); + + it("normalizes trailing slashes", () => { + expect(getBasepath("docs.example.com/docs/")).toBe("/docs"); + }); + + it("preserves the root basepath as /", () => { + expect(getBasepath("docs.example.com/")).toBe("/"); + }); + + it("handles inputs that already include a protocol", () => { + expect(getBasepath("https://docs.example.com/api")).toBe("/api"); + }); + + it("falls back to / for unparseable inputs", () => { + expect(getBasepath("")).toBe("/"); + }); +}); + +describe("validateBasepathAlignment", () => { + it("passes when the instance url and custom domains share a root basepath", () => { + const { context, failAndThrow } = createFakeContext(); + validateBasepathAlignment("acme.docs.buildwithfern.com", ["docs.acme.com"], context); + expect(failAndThrow).not.toHaveBeenCalled(); + }); + + it("passes when the instance url and custom domains share a non-root basepath", () => { + const { context, failAndThrow } = createFakeContext(); + validateBasepathAlignment("acme.docs.buildwithfern.com/docs", ["docs.acme.com/docs", "acme.com/docs"], context); + expect(failAndThrow).not.toHaveBeenCalled(); + }); + + it("fails when the Fern url has a basepath but the custom domain is at the root", () => { + const { context, failAndThrow } = createFakeContext(); + expect(() => + validateBasepathAlignment("acme.docs.buildwithfern.com/docs", ["docs.acme.com"], context) + ).toThrow(); + expect(failAndThrow).toHaveBeenCalledTimes(1); + const [message, , options] = failAndThrow.mock.calls[0] ?? []; + expect(message).toContain("DNS cutover"); + expect(message).toContain("'/docs'"); + expect(message).toContain("'/'"); + expect(options).toEqual({ code: CliError.Code.ConfigError }); + }); + + it("fails when the custom domain has a basepath but the Fern url is at the root", () => { + const { context, failAndThrow } = createFakeContext(); + expect(() => + validateBasepathAlignment("acme.docs.buildwithfern.com", ["docs.acme.com/docs"], context) + ).toThrow(); + expect(failAndThrow).toHaveBeenCalledTimes(1); + }); + + it("fails when the basepaths differ", () => { + const { context, failAndThrow } = createFakeContext(); + expect(() => + validateBasepathAlignment("acme.docs.buildwithfern.com/docs", ["docs.acme.com/api"], context) + ).toThrow(); + expect(failAndThrow).toHaveBeenCalledTimes(1); + }); + + it("treats trailing-slash differences as equivalent", () => { + const { context, failAndThrow } = createFakeContext(); + validateBasepathAlignment("acme.docs.buildwithfern.com/docs/", ["docs.acme.com/docs"], context); + expect(failAndThrow).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/customDomainValidation.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/customDomainValidation.ts new file mode 100644 index 000000000000..3a1a01252ec3 --- /dev/null +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/customDomainValidation.ts @@ -0,0 +1,69 @@ +import { CliError, TaskContext } from "@fern-api/task-context"; + +const HTTPS_PREFIX = "https://"; +const HTTP_PREFIX = "http://"; + +/** + * Strips a leading `https://` or `http://` from a custom domain string. + */ +export function stripCustomDomainProtocol(customDomain: string): string { + if (customDomain.startsWith(HTTPS_PREFIX)) { + return customDomain.slice(HTTPS_PREFIX.length); + } + if (customDomain.startsWith(HTTP_PREFIX)) { + return customDomain.slice(HTTP_PREFIX.length); + } + return customDomain; +} + +/** + * Extracts the basepath (URL pathname) from a domain string. Trailing slashes + * are stripped (except for the root) so `/docs` and `/docs/` compare equal. + */ +export function getBasepath(domain: string): string { + try { + const url = + domain.startsWith(HTTPS_PREFIX) || domain.startsWith(HTTP_PREFIX) ? domain : `${HTTPS_PREFIX}${domain}`; + return normalizeBasepath(new URL(url).pathname); + } catch { + return "/"; + } +} + +function normalizeBasepath(pathname: string): string { + if (pathname === "" || pathname === "/") { + return "/"; + } + return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname; +} + +/** + * Validates that the Fern instance URL and every custom domain share the same + * basepath when basepath-aware mode is enabled (via `multi-source: true` or + * the deprecated `experimental.basepath-aware: true`). + * + * Without this check, docs published under a Fern URL with one basepath but + * fronted by a custom domain with a different basepath will 404 after the + * customer's DNS cutover, since the basepath-aware S3 keys won't line up. + */ +export function validateBasepathAlignment( + instanceUrl: string, + customDomains: readonly string[], + context: TaskContext +): void { + const urlBasepath = getBasepath(instanceUrl); + for (const customDomain of customDomains) { + const customDomainBasepath = getBasepath(customDomain); + if (urlBasepath !== customDomainBasepath) { + context.failAndThrow( + `Basepath mismatch between Fern url and custom-domain. When basepath-aware mode is enabled ` + + `(via 'multi-source: true' or the deprecated 'experimental.basepath-aware: true'), the ` + + `instance 'url' and 'custom-domain' must share the same basepath, otherwise docs will ` + + `fail to resolve after DNS cutover. Instance url '${instanceUrl}' has basepath ` + + `'${urlBasepath}' but custom-domain '${customDomain}' has basepath '${customDomainBasepath}'.`, + undefined, + { code: CliError.Code.ConfigError } + ); + } + } +} diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForDocsWorkspace.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForDocsWorkspace.ts index 8b62e6cea626..e6fc55c32523 100644 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForDocsWorkspace.ts +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForDocsWorkspace.ts @@ -3,6 +3,7 @@ import { replaceEnvVariables } from "@fern-api/core-utils"; import { OSSWorkspace } from "@fern-api/lazy-fern-workspace"; import { CliError, TaskContext } from "@fern-api/task-context"; import { AbstractAPIWorkspace, DocsWorkspace } from "@fern-api/workspace-loader"; +import { stripCustomDomainProtocol, validateBasepathAlignment } from "./customDomainValidation.js"; import { DocsPublishConflictError, publishDocs } from "./publishDocs.js"; const PUBLISH_CONFLICT_RETRY_DELAYS_MS = [ @@ -105,19 +106,19 @@ export async function runRemoteGenerationForDocsWorkspace({ return; } - // TODO: validate custom domains - const customDomains: string[] = []; + const customDomains = ( + typeof maybeInstance.customDomain === "string" + ? [maybeInstance.customDomain] + : (maybeInstance.customDomain ?? []) + ).map(stripCustomDomainProtocol); - if (maybeInstance.customDomain != null) { - if (typeof maybeInstance.customDomain === "string") { - customDomains.push(maybeInstance.customDomain); - } else if (Array.isArray(maybeInstance.customDomain)) { - customDomains.push(...maybeInstance.customDomain); - } - } + // Basepath-aware mode requires the Fern instance url and every custom domain to share the + // same basepath, otherwise docs won't resolve once the customer cuts their DNS over to Fern. + const isBasepathAware = + maybeInstance.multiSource === true || docsWorkspace.config.experimental?.basepathAware === true; - if (maybeInstance.multiSource === true) { - validateMultiSourceBasepaths(maybeInstance.url, customDomains, context); + if (isBasepathAware) { + validateBasepathAlignment(maybeInstance.url, customDomains, context); } context.logger.info(`Starting docs publishing for ${preview ? "preview" : "production"}: ${maybeInstance.url}`); @@ -194,27 +195,3 @@ export async function runRemoteGenerationForDocsWorkspace({ }); return publishedUrl; } - -function getBasepath(domain: string): string { - try { - const url = domain.startsWith("https://") || domain.startsWith("http://") ? domain : `https://${domain}`; - return new URL(url).pathname; - } catch { - return "/"; - } -} - -function validateMultiSourceBasepaths(instanceUrl: string, customDomains: string[], context: TaskContext): void { - const urlBasepath = getBasepath(instanceUrl); - for (const customDomain of customDomains) { - const customDomainBasepath = getBasepath(customDomain); - if (customDomainBasepath !== "/" && urlBasepath !== customDomainBasepath) { - context.failAndThrow( - `When multi-source is enabled, the url and custom-domain must share the same basepath. ` + - `Instance url '${instanceUrl}' has basepath '${urlBasepath}' but custom-domain '${customDomain}' has basepath '${customDomainBasepath}'.`, - undefined, - { code: CliError.Code.ConfigError } - ); - } - } -} diff --git a/packages/cli/posthog-manager/__test__/UserPosthogManager.test.ts b/packages/cli/posthog-manager/__test__/UserPosthogManager.test.ts new file mode 100644 index 000000000000..2f43865e657b --- /dev/null +++ b/packages/cli/posthog-manager/__test__/UserPosthogManager.test.ts @@ -0,0 +1,103 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const { mockAlias, mockCapture, mockDoesPathExist, mockFlush, mockMkdir, mockReadFile, mockUuid, mockWriteFile } = + vi.hoisted(() => ({ + mockAlias: vi.fn(), + mockCapture: vi.fn(), + mockDoesPathExist: vi.fn(async () => false), + mockFlush: vi.fn(async () => undefined), + mockMkdir: vi.fn(async () => undefined), + mockReadFile: vi.fn(async () => Buffer.from("persisted-id")), + mockUuid: vi.fn(() => "generated-id"), + mockWriteFile: vi.fn(async () => undefined) + })); + +vi.mock("@fern-api/cli-telemetry", () => ({ + getRunIdProperties: () => ({}) +})); + +vi.mock("@fern-api/core", () => ({ + createVenusService: vi.fn() +})); + +vi.mock("@fern-api/fs-utils", () => ({ + AbsoluteFilePath: { + of: (path: string) => path + }, + RelativeFilePath: { + of: (path: string) => path + }, + doesPathExist: mockDoesPathExist, + join: (...parts: string[]) => parts.join("/") +})); + +vi.mock("fs/promises", () => ({ + mkdir: mockMkdir, + readFile: mockReadFile, + writeFile: mockWriteFile +})); + +vi.mock("os", () => ({ + homedir: () => "/Users/test" +})); + +vi.mock("posthog-node", () => ({ + PostHog: class { + public readonly alias = mockAlias; + public readonly capture = mockCapture; + public readonly flush = mockFlush; + } +})); + +vi.mock("uuid", () => ({ + v4: mockUuid +})); + +import { UserPosthogManager } from "../src/UserPosthogManager.js"; + +describe("UserPosthogManager", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockDoesPathExist.mockResolvedValue(false); + mockMkdir.mockResolvedValue(undefined); + mockReadFile.mockResolvedValue(Buffer.from("persisted-id")); + mockUuid.mockReturnValue("generated-id"); + mockWriteFile.mockResolvedValue(undefined); + }); + + it("falls back to an in-memory distinct ID when persisted ID storage is not writable", async () => { + const manager = new UserPosthogManager({ token: undefined, posthogApiKey: "test-api-key" }); + mockMkdir.mockRejectedValue( + Object.assign(new Error("EPERM: operation not permitted, mkdir"), { code: "EPERM" }) + ); + + await expect(manager.sendEvent({ command: "check" })).resolves.toBeUndefined(); + + expect(mockCapture).toHaveBeenCalledWith( + expect.objectContaining({ + distinctId: "generated-id" + }) + ); + expect(mockWriteFile).not.toHaveBeenCalled(); + expect(mockReadFile).not.toHaveBeenCalled(); + }); + + it("reuses the in-memory fallback after a storage failure", async () => { + const manager = new UserPosthogManager({ token: undefined, posthogApiKey: "test-api-key" }); + mockMkdir.mockRejectedValue( + Object.assign(new Error("EPERM: operation not permitted, mkdir"), { code: "EPERM" }) + ); + + await manager.sendEvent({ command: "check" }); + await manager.sendEvent({ command: "generate" }); + + expect(mockMkdir).toHaveBeenCalledTimes(1); + expect(mockCapture).toHaveBeenCalledTimes(2); + expect(mockCapture).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + distinctId: "generated-id" + }) + ); + }); +}); diff --git a/packages/cli/posthog-manager/src/UserPosthogManager.ts b/packages/cli/posthog-manager/src/UserPosthogManager.ts index 7f996caf35ae..4f19750c01cd 100644 --- a/packages/cli/posthog-manager/src/UserPosthogManager.ts +++ b/packages/cli/posthog-manager/src/UserPosthogManager.ts @@ -94,16 +94,24 @@ export class UserPosthogManager implements PosthogManager { private persistedDistinctId: string | undefined; private async getPersistedDistinctId(): Promise { if (this.persistedDistinctId == null) { + const generatedDistinctId = uuidv4(); const pathToFile = join( AbsoluteFilePath.of(homedir()), RelativeFilePath.of(LOCAL_STORAGE_FOLDER), RelativeFilePath.of(DISTINCT_ID_FILENAME) ); - if (!(await doesPathExist(pathToFile))) { - await mkdir(dirname(pathToFile), { recursive: true }); - await writeFile(pathToFile, uuidv4()); + try { + if (!(await doesPathExist(pathToFile))) { + await mkdir(dirname(pathToFile), { recursive: true }); + await writeFile(pathToFile, generatedDistinctId); + this.persistedDistinctId = generatedDistinctId; + } else { + this.persistedDistinctId = (await readFile(pathToFile)).toString(); + } + } catch { + // Analytics should never block CLI execution if the user's home directory is not writable. + this.persistedDistinctId = generatedDistinctId; } - this.persistedDistinctId = (await readFile(pathToFile)).toString(); } return this.persistedDistinctId; } diff --git a/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts b/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts index 0c91e3373ce5..44e66170ff99 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts +++ b/packages/cli/register/src/ir-to-fdr-converter/convertPackage.ts @@ -510,7 +510,7 @@ function convertIrEnvironments({ if (endpointBaseUrl == null) { throw new CliError({ message: `Encountered undefined server name "${baseUrlId}" at endpoint ${endpoint.method.toUpperCase()} ${endpoint.path}. Expected environment ${singleBaseUrlEnvironment.id} to contain url for ${baseUrlId}`, - code: CliError.Code.IrConversionError + code: CliError.Code.ValidationError }); } return { @@ -536,7 +536,7 @@ function convertIrEnvironments({ if (endpointBaseUrl == null) { throw new CliError({ message: `Encountered undefined server name "${endpointBaseUrlId}" at endpoint ${endpoint.method.toUpperCase()} ${endpoint.path.head}. Expected environment ${singleBaseUrlEnvironment.id} to contain url for ${endpointBaseUrlId}`, - code: CliError.Code.IrConversionError + code: CliError.Code.ValidationError }); } return { diff --git a/packages/seed/src/commands/test/test-runner/ContainerTestRunner.ts b/packages/seed/src/commands/test/test-runner/ContainerTestRunner.ts index 8a5dc3380451..7d9ef4e33902 100644 --- a/packages/seed/src/commands/test/test-runner/ContainerTestRunner.ts +++ b/packages/seed/src/commands/test/test-runner/ContainerTestRunner.ts @@ -153,7 +153,8 @@ export class ContainerTestRunner extends TestRunner { smartCasing, organization, absolutePathToFernConfig, - skipAutogenerationIfManualExamplesExist + skipAutogenerationIfManualExamplesExist, + rawApiSpecs }: TestRunner.DoRunArgs): Promise { const generatorGroup: generatorsYml.GeneratorGroup = { groupName: "test", @@ -196,6 +197,7 @@ export class ContainerTestRunner extends TestRunner { executionEnvironment: this.reusableContainer, ai: undefined, skipAutogenerationIfManualExamplesExist, + rawApiSpecs, verify: verifyEnabled, verifyRunner: this.runner, // Generator runs at :local but the validator image isn't built locally; diff --git a/packages/seed/src/commands/test/test-runner/LocalTestRunner.ts b/packages/seed/src/commands/test/test-runner/LocalTestRunner.ts index 7bd062af7133..0314983715db 100644 --- a/packages/seed/src/commands/test/test-runner/LocalTestRunner.ts +++ b/packages/seed/src/commands/test/test-runner/LocalTestRunner.ts @@ -59,7 +59,8 @@ export class LocalTestRunner extends TestRunner { smartCasing, organization, absolutePathToFernConfig, - skipAutogenerationIfManualExamplesExist + skipAutogenerationIfManualExamplesExist, + rawApiSpecs } = args; const generatorGroup: generatorsYml.GeneratorGroup = { @@ -102,6 +103,7 @@ export class LocalTestRunner extends TestRunner { inspect, ai: undefined, skipAutogenerationIfManualExamplesExist, + rawApiSpecs, verify: verifyEnabled, // Generator runs at :local but the validator image isn't built locally; // pull the published :latest validator. This mirrors what a customer's diff --git a/packages/seed/src/commands/test/test-runner/TestRunner.ts b/packages/seed/src/commands/test/test-runner/TestRunner.ts index 7db794c4779e..28921b2ef6a3 100644 --- a/packages/seed/src/commands/test/test-runner/TestRunner.ts +++ b/packages/seed/src/commands/test/test-runner/TestRunner.ts @@ -1,6 +1,8 @@ -import { FernWorkspace } from "@fern-api/api-workspace-commons"; +import { FernWorkspace, type Spec } from "@fern-api/api-workspace-commons"; import { APIS_DIRECTORY, FERN_DIRECTORY, GeneratorInvocation, generatorsYml } from "@fern-api/configuration"; import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils"; +import { OSSWorkspace } from "@fern-api/lazy-fern-workspace"; +import { generatorWantsSpecs } from "@fern-api/local-workspace-runner"; import { LogLevel } from "@fern-api/logger"; import { TaskContext, TaskResult } from "@fern-api/task-context"; import { getBaseOpenAPIWorkspaceSettingsFromGeneratorInvocation } from "@fern-api/workspace-loader"; @@ -79,6 +81,8 @@ export declare namespace TestRunner { absolutePathToFernConfig?: AbsoluteFilePath; /** If true, skip autogenerated examples when manual examples exist */ skipAutogenerationIfManualExamplesExist?: boolean; + /** Raw API spec files to pre-process and mount into the generator container */ + rawApiSpecs?: Spec[]; } type TestResult = TestSuccess | TestFailure; @@ -230,6 +234,8 @@ export abstract class TestRunner { const smartCasing = generatorInvocation?.smartCasing; let fernWorkspace: FernWorkspace | undefined; + let rawApiSpecs: Spec[] | undefined; + const wantsSpecs = generatorWantsSpecs(this.getParsedDockerImageName().name); if (this.workspaceCache != null && generatorInvocation == null) { // Use cache when no generatorInvocation overrides are present. // The cache is keyed by absolutePathToAPIDefinition (derived from fixture name), @@ -239,6 +245,16 @@ export abstract class TestRunner { absolutePathToAPIDefinition: absolutePathToApiDefinition, taskContext }); + if (wantsSpecs) { + const cachedApiWorkspace = await this.workspaceCache.getOrLoadApiWorkspace({ + fixture, + absolutePathToAPIDefinition: absolutePathToApiDefinition, + taskContext + }); + if (cachedApiWorkspace instanceof OSSWorkspace) { + rawApiSpecs = cachedApiWorkspace.allSpecs; + } + } } else { // Fallback to uncached loading when generatorInvocation may provide // custom workspaceSettings or apiOverride specs. @@ -248,6 +264,11 @@ export abstract class TestRunner { fixture, lenient }); + // Extract raw spec file paths before conversion to FernWorkspace + // (OSSWorkspace has allSpecs; after toFernWorkspace() the paths are lost) + if (wantsSpecs && apiWorkspace instanceof OSSWorkspace) { + rawApiSpecs = apiWorkspace.allSpecs; + } const workspaceSettings = generatorInvocation != null ? getBaseOpenAPIWorkspaceSettingsFromGeneratorInvocation(generatorInvocation) @@ -303,7 +324,8 @@ export abstract class TestRunner { smartCasing, organization, absolutePathToFernConfig, - skipAutogenerationIfManualExamplesExist + skipAutogenerationIfManualExamplesExist, + rawApiSpecs }); generationStopwatch.stop(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e281e1f70f3..64e610ae9c7b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,8 +37,8 @@ catalogs: specifier: 0.0.6-2ee1b7e28 version: 0.0.6-2ee1b7e28 '@fern-api/generator-cli': - specifier: 0.9.32 - version: 0.9.32 + specifier: 0.9.33 + version: 0.9.33 '@fern-api/venus-api-sdk': specifier: 0.22.34 version: 0.22.34 @@ -630,7 +630,7 @@ importers: version: link:packages/configs '@fern-api/generator-cli': specifier: 'catalog:' - version: 0.9.32 + version: 0.9.33 '@rolldown/binding-darwin-arm64': specifier: 'catalog:' version: 1.0.0 @@ -699,7 +699,7 @@ importers: version: link:../../packages/commons/fs-utils '@fern-api/generator-cli': specifier: 'catalog:' - version: 0.9.32 + version: 0.9.33 '@fern-api/ir-sdk': specifier: workspace:* version: link:../../packages/ir-sdk @@ -2466,7 +2466,7 @@ importers: version: link:../../../packages/commons/fs-utils '@fern-api/generator-cli': specifier: 'catalog:' - version: 0.9.32 + version: 0.9.33 '@fern-api/logger': specifier: workspace:* version: link:../../../packages/cli/logger @@ -6182,6 +6182,9 @@ importers: decompress: specifier: 'catalog:' version: 4.2.1 + js-yaml: + specifier: 'catalog:' + version: 4.1.1 semver: specifier: ^7.6.3 version: 7.7.4 @@ -6195,6 +6198,9 @@ importers: '@types/decompress': specifier: 'catalog:' version: 4.2.7 + '@types/js-yaml': + specifier: 'catalog:' + version: 4.0.9 '@types/node': specifier: 'catalog:' version: 22.19.17 @@ -9474,13 +9480,8 @@ packages: '@fern-api/fdr-sdk@1.2.4-f661387fb2': resolution: {integrity: sha512-wlk1lTCIZ7biND4vQf8jvhUw9P/rBQ5pXASCrumv8R96up0B3DY6yiY1C4VmFyHmp/kPhcjzc5T9TvHZZxFdrA==} - '@fern-api/generator-cli@0.9.32': - resolution: {integrity: sha512-i51Wp7iGPOj0xUIPFQg1SCzzojV/5jWgkq4Pp+VnPG8x2xmHUE65ZgdRWdpu3em6pqFNLCHfW/so2VKrxal4ag==} - hasBin: true - - '@fern-api/replay@0.16.0': - resolution: {integrity: sha512-i4TC4/UJtju9hX9aDWMlHXMPJ07lM0XD8cQLawYcDInY3LDOmnzqExoByWJb/m4AvhfxY2VFlITtCr0mVPethg==} - engines: {node: '>=18'} + '@fern-api/generator-cli@0.9.33': + resolution: {integrity: sha512-KG5lQEbJr5x2Z7Ggbzyv/2/JGCSJ5IYBMFFYVhzx2MCf+XJPhw+G4X1l199M7PCrIoNXKD7WBiqCmQkgiKSjQQ==} hasBin: true '@fern-api/replay@0.16.1': @@ -16416,10 +16417,10 @@ snapshots: - encoding - typescript - '@fern-api/generator-cli@0.9.32': + '@fern-api/generator-cli@0.9.33': dependencies: '@boundaryml/baml': 0.219.0 - '@fern-api/replay': 0.16.0 + '@fern-api/replay': 0.16.1 '@octokit/rest': 22.0.1 es-toolkit: 1.45.1 semver: 7.7.4 @@ -16427,15 +16428,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@fern-api/replay@0.16.0': - dependencies: - minimatch: 10.2.5 - node-diff3: 3.2.0 - simple-git: 3.36.0 - yaml: 2.8.3 - transitivePeerDependencies: - - supports-color - '@fern-api/replay@0.16.1': dependencies: minimatch: 10.2.5 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 32b04b8d570f..5f8095fec254 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -68,7 +68,7 @@ catalog: "@bufbuild/protoplugin": 2.2.5 "@fern-api/fai-sdk": 0.0.6-2ee1b7e28 "@fern-api/fdr-sdk": 1.2.4-f661387fb2 - "@fern-api/generator-cli": 0.9.32 + "@fern-api/generator-cli": 0.9.33 "@fern-api/ui-core-utils": 0.129.4-b6c699ad2 "@fern-api/venus-api-sdk": 0.22.34 "@fern-fern/docs-config": 0.0.80 diff --git a/seed/php-sdk/accept-header/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/accept-header/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/accept-header/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/accept-header/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/accept-header/tests/Core/Client/RawClientTest.php b/seed/php-sdk/accept-header/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/accept-header/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/accept-header/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/alias-extends/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/alias-extends/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/alias-extends/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/alias-extends/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/alias-extends/tests/Core/Client/RawClientTest.php b/seed/php-sdk/alias-extends/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/alias-extends/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/alias-extends/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/alias/composer-json/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/alias/composer-json/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/alias/composer-json/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/alias/composer-json/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/alias/composer-json/tests/Core/Client/RawClientTest.php b/seed/php-sdk/alias/composer-json/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/alias/composer-json/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/alias/composer-json/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/alias/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/alias/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/alias/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/alias/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/alias/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/alias/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/alias/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/alias/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/allof-inline/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php b/seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/allof-inline/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/allof/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/allof/tests/Core/Client/RawClientTest.php b/seed/php-sdk/allof/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/allof/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/allof/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/any-auth/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/any-auth/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/any-auth/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/any-auth/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/any-auth/tests/Core/Client/RawClientTest.php b/seed/php-sdk/any-auth/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/any-auth/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/any-auth/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/api-wide-base-path-with-default/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/api-wide-base-path-with-default/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/api-wide-base-path-with-default/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/api-wide-base-path-with-default/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/api-wide-base-path-with-default/tests/Core/Client/RawClientTest.php b/seed/php-sdk/api-wide-base-path-with-default/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/api-wide-base-path-with-default/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/api-wide-base-path-with-default/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/api-wide-base-path/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/api-wide-base-path/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/api-wide-base-path/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/api-wide-base-path/tests/Core/Client/RawClientTest.php b/seed/php-sdk/api-wide-base-path/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/api-wide-base-path/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/api-wide-base-path/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/audiences/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/audiences/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/audiences/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/audiences/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/audiences/tests/Core/Client/RawClientTest.php b/seed/php-sdk/audiences/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/audiences/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/audiences/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Core/Client/RawClientTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/basic-auth-pw-omitted/wire-tests/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/basic-auth-pw-omitted/wire-tests/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/basic-auth-pw-omitted/wire-tests/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/basic-auth-pw-omitted/wire-tests/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/basic-auth-pw-omitted/wire-tests/tests/Core/Client/RawClientTest.php b/seed/php-sdk/basic-auth-pw-omitted/wire-tests/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/basic-auth-pw-omitted/wire-tests/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/basic-auth-pw-omitted/wire-tests/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/basic-auth/wire-tests/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/basic-auth/wire-tests/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/basic-auth/wire-tests/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/basic-auth/wire-tests/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/basic-auth/wire-tests/tests/Core/Client/RawClientTest.php b/seed/php-sdk/basic-auth/wire-tests/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/basic-auth/wire-tests/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/basic-auth/wire-tests/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Core/Client/RawClientTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/bytes-download/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/bytes-download/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/bytes-download/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/bytes-download/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/bytes-download/tests/Core/Client/RawClientTest.php b/seed/php-sdk/bytes-download/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/bytes-download/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/bytes-download/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/bytes-upload/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/bytes-upload/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/bytes-upload/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/bytes-upload/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/bytes-upload/tests/Core/Client/RawClientTest.php b/seed/php-sdk/bytes-upload/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/bytes-upload/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/bytes-upload/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/circular-references-advanced/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/circular-references-advanced/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/circular-references-advanced/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/circular-references-advanced/tests/Core/Client/RawClientTest.php b/seed/php-sdk/circular-references-advanced/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/circular-references-advanced/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/circular-references-advanced/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/circular-references-extends/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/circular-references-extends/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/circular-references-extends/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/circular-references-extends/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/circular-references-extends/tests/Core/Client/RawClientTest.php b/seed/php-sdk/circular-references-extends/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/circular-references-extends/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/circular-references-extends/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/circular-references/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/circular-references/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/circular-references/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/circular-references/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/circular-references/tests/Core/Client/RawClientTest.php b/seed/php-sdk/circular-references/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/circular-references/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/circular-references/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/client-side-params/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/client-side-params/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/client-side-params/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/client-side-params/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/client-side-params/tests/Core/Client/RawClientTest.php b/seed/php-sdk/client-side-params/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/client-side-params/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/client-side-params/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/content-type/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/content-type/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/content-type/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/content-type/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/content-type/tests/Core/Client/RawClientTest.php b/seed/php-sdk/content-type/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/content-type/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/content-type/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/cross-package-type-names/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/cross-package-type-names/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/cross-package-type-names/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/cross-package-type-names/tests/Core/Client/RawClientTest.php b/seed/php-sdk/cross-package-type-names/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/cross-package-type-names/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/cross-package-type-names/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/dollar-string-examples/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/dollar-string-examples/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/dollar-string-examples/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/dollar-string-examples/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/dollar-string-examples/tests/Core/Client/RawClientTest.php b/seed/php-sdk/dollar-string-examples/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/dollar-string-examples/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/dollar-string-examples/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/empty-clients/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/empty-clients/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/empty-clients/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/empty-clients/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/empty-clients/tests/Core/Client/RawClientTest.php b/seed/php-sdk/empty-clients/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/empty-clients/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/empty-clients/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/endpoint-security-auth/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/endpoint-security-auth/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/endpoint-security-auth/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/endpoint-security-auth/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/endpoint-security-auth/tests/Core/Client/RawClientTest.php b/seed/php-sdk/endpoint-security-auth/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/endpoint-security-auth/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/endpoint-security-auth/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/enum/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/enum/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/enum/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/enum/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/enum/tests/Core/Client/RawClientTest.php b/seed/php-sdk/enum/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/enum/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/enum/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/error-property/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/error-property/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/error-property/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/error-property/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/error-property/tests/Core/Client/RawClientTest.php b/seed/php-sdk/error-property/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/error-property/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/error-property/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/errors/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/errors/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/errors/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/errors/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/errors/tests/Core/Client/RawClientTest.php b/seed/php-sdk/errors/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/errors/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/errors/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/examples/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/examples/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/examples/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/examples/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/examples/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/examples/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/examples/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/examples/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/examples/readme-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/examples/readme-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/examples/readme-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/examples/readme-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/examples/readme-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/examples/readme-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/examples/readme-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/examples/readme-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/exhaustive/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/exhaustive/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/exhaustive/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/exhaustive/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/exhaustive/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/exhaustive/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/exhaustive/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/exhaustive/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/exhaustive/wire-tests/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/exhaustive/wire-tests/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/exhaustive/wire-tests/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/exhaustive/wire-tests/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Core/Client/RawClientTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/extends/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/extends/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/extends/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/extends/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/extends/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/extends/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/extends/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/extends/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/extends/private/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/extends/private/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/extends/private/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/extends/private/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/extends/private/tests/Core/Client/RawClientTest.php b/seed/php-sdk/extends/private/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/extends/private/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/extends/private/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/extra-properties/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/extra-properties/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/extra-properties/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/extra-properties/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/extra-properties/tests/Core/Client/RawClientTest.php b/seed/php-sdk/extra-properties/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/extra-properties/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/extra-properties/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/file-download/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/file-download/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/file-download/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/file-download/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/file-download/tests/Core/Client/RawClientTest.php b/seed/php-sdk/file-download/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/file-download/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/file-download/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/file-upload-openapi/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/file-upload-openapi/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/file-upload-openapi/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/file-upload-openapi/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/file-upload-openapi/tests/Core/Client/RawClientTest.php b/seed/php-sdk/file-upload-openapi/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/file-upload-openapi/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/file-upload-openapi/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/file-upload/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/file-upload/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/file-upload/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/file-upload/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/file-upload/tests/Core/Client/RawClientTest.php b/seed/php-sdk/file-upload/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/file-upload/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/file-upload/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/folders/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/folders/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/folders/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/folders/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/folders/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/folders/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/folders/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/folders/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/folders/with-interfaces/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/folders/with-interfaces/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/folders/with-interfaces/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/folders/with-interfaces/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/folders/with-interfaces/tests/Core/Client/RawClientTest.php b/seed/php-sdk/folders/with-interfaces/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/folders/with-interfaces/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/folders/with-interfaces/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/header-auth-environment-variable/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/header-auth-environment-variable/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/header-auth-environment-variable/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/header-auth-environment-variable/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/header-auth-environment-variable/tests/Core/Client/RawClientTest.php b/seed/php-sdk/header-auth-environment-variable/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/header-auth-environment-variable/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/header-auth-environment-variable/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/header-auth/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/header-auth/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/header-auth/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/header-auth/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/header-auth/tests/Core/Client/RawClientTest.php b/seed/php-sdk/header-auth/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/header-auth/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/header-auth/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/http-head/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/http-head/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/http-head/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/http-head/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/http-head/tests/Core/Client/RawClientTest.php b/seed/php-sdk/http-head/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/http-head/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/http-head/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/idempotency-headers/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/idempotency-headers/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/idempotency-headers/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/idempotency-headers/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/idempotency-headers/tests/Core/Client/RawClientTest.php b/seed/php-sdk/idempotency-headers/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/idempotency-headers/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/idempotency-headers/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/imdb/clientName/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/imdb/clientName/src/Core/Client/RetryDecoratingClient.php index 0e39edb1cfec..af53a9fc8b51 100644 --- a/seed/php-sdk/imdb/clientName/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/imdb/clientName/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/imdb/clientName/tests/Core/Client/RawClientTest.php b/seed/php-sdk/imdb/clientName/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/imdb/clientName/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/imdb/clientName/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/imdb/namespace/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/imdb/namespace/src/Core/Client/RetryDecoratingClient.php index f1beb35abf67..e5b2e208fa8a 100644 --- a/seed/php-sdk/imdb/namespace/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/imdb/namespace/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/imdb/namespace/tests/Core/Client/RawClientTest.php b/seed/php-sdk/imdb/namespace/tests/Core/Client/RawClientTest.php index cb64c893e58d..6dd3f2eace5f 100644 --- a/seed/php-sdk/imdb/namespace/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/imdb/namespace/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/imdb/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/imdb/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/imdb/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/imdb/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/imdb/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/imdb/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/imdb/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/imdb/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/imdb/omit-fern-headers/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/imdb/omit-fern-headers/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/imdb/omit-fern-headers/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/imdb/omit-fern-headers/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/imdb/omit-fern-headers/tests/Core/Client/RawClientTest.php b/seed/php-sdk/imdb/omit-fern-headers/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/imdb/omit-fern-headers/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/imdb/omit-fern-headers/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/imdb/package-path/src/Custom/Package/Path/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/imdb/package-path/src/Custom/Package/Path/Core/Client/RetryDecoratingClient.php index 29d222092b0f..5704fb8f818a 100644 --- a/seed/php-sdk/imdb/package-path/src/Custom/Package/Path/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/imdb/package-path/src/Custom/Package/Path/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/imdb/package-path/tests/Custom/Package/Path/Core/Client/RawClientTest.php b/seed/php-sdk/imdb/package-path/tests/Custom/Package/Path/Core/Client/RawClientTest.php index 9003f4e28e4d..bdce99a9f518 100644 --- a/seed/php-sdk/imdb/package-path/tests/Custom/Package/Path/Core/Client/RawClientTest.php +++ b/seed/php-sdk/imdb/package-path/tests/Custom/Package/Path/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/imdb/packageName/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/imdb/packageName/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/imdb/packageName/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/imdb/packageName/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/imdb/packageName/tests/Core/Client/RawClientTest.php b/seed/php-sdk/imdb/packageName/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/imdb/packageName/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/imdb/packageName/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/imdb/private/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/imdb/private/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/imdb/private/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/imdb/private/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/imdb/private/tests/Core/Client/RawClientTest.php b/seed/php-sdk/imdb/private/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/imdb/private/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/imdb/private/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/inferred-auth-explicit/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/inferred-auth-explicit/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/inferred-auth-explicit/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/inferred-auth-explicit/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/inferred-auth-explicit/tests/Core/Client/RawClientTest.php b/seed/php-sdk/inferred-auth-explicit/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/inferred-auth-explicit/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/inferred-auth-explicit/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/inferred-auth-implicit-api-key/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/inferred-auth-implicit-api-key/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/inferred-auth-implicit-api-key/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/inferred-auth-implicit-api-key/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/inferred-auth-implicit-api-key/tests/Core/Client/RawClientTest.php b/seed/php-sdk/inferred-auth-implicit-api-key/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/inferred-auth-implicit-api-key/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/inferred-auth-implicit-api-key/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/inferred-auth-implicit-no-expiry/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/inferred-auth-implicit-no-expiry/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/inferred-auth-implicit-no-expiry/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/inferred-auth-implicit-no-expiry/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/inferred-auth-implicit-no-expiry/tests/Core/Client/RawClientTest.php b/seed/php-sdk/inferred-auth-implicit-no-expiry/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/inferred-auth-implicit-no-expiry/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/inferred-auth-implicit-no-expiry/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/inferred-auth-implicit-reference/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/inferred-auth-implicit-reference/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/inferred-auth-implicit-reference/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/inferred-auth-implicit-reference/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/inferred-auth-implicit-reference/tests/Core/Client/RawClientTest.php b/seed/php-sdk/inferred-auth-implicit-reference/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/inferred-auth-implicit-reference/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/inferred-auth-implicit-reference/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/inferred-auth-implicit/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/inferred-auth-implicit/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/inferred-auth-implicit/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/inferred-auth-implicit/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/inferred-auth-implicit/tests/Core/Client/RawClientTest.php b/seed/php-sdk/inferred-auth-implicit/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/inferred-auth-implicit/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/inferred-auth-implicit/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/license/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/license/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/license/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/license/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/license/tests/Core/Client/RawClientTest.php b/seed/php-sdk/license/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/license/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/license/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/literal-user-agent/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/literal-user-agent/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/literal-user-agent/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/literal-user-agent/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/literal-user-agent/tests/Core/Client/RawClientTest.php b/seed/php-sdk/literal-user-agent/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/literal-user-agent/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/literal-user-agent/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/literal/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/literal/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/literal/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/literal/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/literal/tests/Core/Client/RawClientTest.php b/seed/php-sdk/literal/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/literal/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/literal/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/literals-unions/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/literals-unions/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/literals-unions/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/literals-unions/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/literals-unions/tests/Core/Client/RawClientTest.php b/seed/php-sdk/literals-unions/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/literals-unions/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/literals-unions/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/mixed-case/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/mixed-case/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/mixed-case/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/mixed-case/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/mixed-case/tests/Core/Client/RawClientTest.php b/seed/php-sdk/mixed-case/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/mixed-case/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/mixed-case/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/mixed-file-directory/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/mixed-file-directory/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/mixed-file-directory/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/mixed-file-directory/tests/Core/Client/RawClientTest.php b/seed/php-sdk/mixed-file-directory/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/mixed-file-directory/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/mixed-file-directory/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/multi-line-docs/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/multi-line-docs/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/multi-line-docs/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/multi-line-docs/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/multi-line-docs/tests/Core/Client/RawClientTest.php b/seed/php-sdk/multi-line-docs/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/multi-line-docs/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/multi-line-docs/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/multi-url-environment-no-default/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/multi-url-environment-no-default/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/multi-url-environment-no-default/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/multi-url-environment-no-default/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/multi-url-environment-no-default/tests/Core/Client/RawClientTest.php b/seed/php-sdk/multi-url-environment-no-default/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/multi-url-environment-no-default/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/multi-url-environment-no-default/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/multi-url-environment-reference/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/multi-url-environment-reference/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/multi-url-environment-reference/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/multi-url-environment-reference/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/multi-url-environment-reference/tests/Core/Client/RawClientTest.php b/seed/php-sdk/multi-url-environment-reference/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/multi-url-environment-reference/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/multi-url-environment-reference/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/multi-url-environment/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/multi-url-environment/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/multi-url-environment/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/multi-url-environment/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/multi-url-environment/tests/Core/Client/RawClientTest.php b/seed/php-sdk/multi-url-environment/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/multi-url-environment/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/multi-url-environment/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/multiple-request-bodies/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/multiple-request-bodies/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/multiple-request-bodies/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/multiple-request-bodies/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/multiple-request-bodies/tests/Core/Client/RawClientTest.php b/seed/php-sdk/multiple-request-bodies/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/multiple-request-bodies/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/multiple-request-bodies/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/no-content-response/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/no-content-response/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/no-content-response/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/no-content-response/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/no-content-response/tests/Core/Client/RawClientTest.php b/seed/php-sdk/no-content-response/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/no-content-response/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/no-content-response/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/no-environment/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/no-environment/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/no-environment/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/no-environment/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/no-environment/tests/Core/Client/RawClientTest.php b/seed/php-sdk/no-environment/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/no-environment/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/no-environment/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/no-retries/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/no-retries/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/no-retries/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/no-retries/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/no-retries/tests/Core/Client/RawClientTest.php b/seed/php-sdk/no-retries/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/no-retries/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/no-retries/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/null-type/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/null-type/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/null-type/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/null-type/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/null-type/tests/Core/Client/RawClientTest.php b/seed/php-sdk/null-type/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/null-type/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/null-type/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/nullable-allof-extends/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/nullable-allof-extends/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/nullable-allof-extends/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/nullable-allof-extends/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/nullable-allof-extends/tests/Core/Client/RawClientTest.php b/seed/php-sdk/nullable-allof-extends/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/nullable-allof-extends/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/nullable-allof-extends/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/nullable-optional/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/nullable-optional/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/nullable-optional/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/nullable-optional/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/nullable-optional/tests/Core/Client/RawClientTest.php b/seed/php-sdk/nullable-optional/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/nullable-optional/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/nullable-optional/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/nullable-request-body/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/nullable-request-body/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/nullable-request-body/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/nullable-request-body/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/nullable-request-body/tests/Core/Client/RawClientTest.php b/seed/php-sdk/nullable-request-body/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/nullable-request-body/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/nullable-request-body/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/nullable/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/nullable/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/nullable/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/nullable/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/nullable/tests/Core/Client/RawClientTest.php b/seed/php-sdk/nullable/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/nullable/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/nullable/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/oauth-client-credentials-custom/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/oauth-client-credentials-custom/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/oauth-client-credentials-custom/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/oauth-client-credentials-custom/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/oauth-client-credentials-custom/tests/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-custom/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/oauth-client-credentials-custom/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/oauth-client-credentials-custom/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/oauth-client-credentials-mandatory-auth/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/oauth-client-credentials-mandatory-auth/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/oauth-client-credentials-mandatory-auth/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/oauth-client-credentials-mandatory-auth/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/oauth-client-credentials-mandatory-auth/tests/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-mandatory-auth/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/oauth-client-credentials-mandatory-auth/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/oauth-client-credentials-mandatory-auth/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/oauth-client-credentials-openapi/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/oauth-client-credentials-openapi/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/oauth-client-credentials-openapi/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/oauth-client-credentials-openapi/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/oauth-client-credentials-openapi/tests/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-openapi/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/oauth-client-credentials-openapi/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/oauth-client-credentials-openapi/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/oauth-client-credentials-reference/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/oauth-client-credentials-reference/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/oauth-client-credentials-reference/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/oauth-client-credentials-reference/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/oauth-client-credentials-reference/tests/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-reference/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/oauth-client-credentials-reference/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/oauth-client-credentials-reference/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/oauth-client-credentials-with-variables/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/oauth-client-credentials-with-variables/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/oauth-client-credentials-with-variables/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/oauth-client-credentials-with-variables/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/oauth-client-credentials-with-variables/tests/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-with-variables/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/oauth-client-credentials-with-variables/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/oauth-client-credentials-with-variables/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/oauth-client-credentials/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/oauth-client-credentials/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/oauth-client-credentials/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/oauth-client-credentials/tests/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/oauth-client-credentials/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/oauth-client-credentials/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/object/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/object/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/object/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/object/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/object/tests/Core/Client/RawClientTest.php b/seed/php-sdk/object/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/object/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/object/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/objects-with-imports/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/objects-with-imports/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/objects-with-imports/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/objects-with-imports/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/objects-with-imports/tests/Core/Client/RawClientTest.php b/seed/php-sdk/objects-with-imports/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/objects-with-imports/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/objects-with-imports/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/openapi-request-body-ref/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/openapi-request-body-ref/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/openapi-request-body-ref/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/openapi-request-body-ref/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/openapi-request-body-ref/tests/Core/Client/RawClientTest.php b/seed/php-sdk/openapi-request-body-ref/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/openapi-request-body-ref/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/openapi-request-body-ref/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/optional/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/optional/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/optional/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/optional/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/optional/tests/Core/Client/RawClientTest.php b/seed/php-sdk/optional/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/optional/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/optional/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/package-yml/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/package-yml/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/package-yml/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/package-yml/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/package-yml/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/package-yml/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/package-yml/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/package-yml/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/package-yml/private/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/package-yml/private/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/package-yml/private/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/package-yml/private/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/package-yml/private/tests/Core/Client/RawClientTest.php b/seed/php-sdk/package-yml/private/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/package-yml/private/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/package-yml/private/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/pagination-custom/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/pagination-custom/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/pagination-custom/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/pagination-custom/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/pagination-custom/tests/Core/Client/RawClientTest.php b/seed/php-sdk/pagination-custom/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/pagination-custom/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/pagination-custom/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/pagination-uri-path/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/pagination-uri-path/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/pagination-uri-path/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/pagination-uri-path/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/pagination-uri-path/tests/Core/Client/RawClientTest.php b/seed/php-sdk/pagination-uri-path/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/pagination-uri-path/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/pagination-uri-path/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/pagination/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/pagination/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/pagination/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/pagination/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/pagination/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/pagination/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/pagination/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/pagination/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/pagination/page-index-semantics/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/pagination/page-index-semantics/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/pagination/page-index-semantics/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/pagination/page-index-semantics/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/pagination/page-index-semantics/tests/Core/Client/RawClientTest.php b/seed/php-sdk/pagination/page-index-semantics/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/pagination/page-index-semantics/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/pagination/page-index-semantics/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/pagination/property-accessors/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/pagination/property-accessors/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/pagination/property-accessors/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/pagination/property-accessors/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/pagination/property-accessors/tests/Core/Client/RawClientTest.php b/seed/php-sdk/pagination/property-accessors/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/pagination/property-accessors/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/pagination/property-accessors/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/path-parameters/inline-path-parameters-private/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/path-parameters/inline-path-parameters-private/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/path-parameters/inline-path-parameters-private/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/path-parameters/inline-path-parameters-private/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/path-parameters/inline-path-parameters-private/tests/Core/Client/RawClientTest.php b/seed/php-sdk/path-parameters/inline-path-parameters-private/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/path-parameters/inline-path-parameters-private/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/path-parameters/inline-path-parameters-private/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/path-parameters/inline-path-parameters/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/path-parameters/inline-path-parameters/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/path-parameters/inline-path-parameters/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/path-parameters/inline-path-parameters/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/path-parameters/inline-path-parameters/tests/Core/Client/RawClientTest.php b/seed/php-sdk/path-parameters/inline-path-parameters/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/path-parameters/inline-path-parameters/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/path-parameters/inline-path-parameters/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/path-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/path-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/path-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/path-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/path-parameters/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/path-parameters/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/path-parameters/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/path-parameters/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/plain-text/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/plain-text/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/plain-text/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/plain-text/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/plain-text/tests/Core/Client/RawClientTest.php b/seed/php-sdk/plain-text/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/plain-text/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/plain-text/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/property-access/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/property-access/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/property-access/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/property-access/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/property-access/tests/Core/Client/RawClientTest.php b/seed/php-sdk/property-access/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/property-access/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/property-access/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/public-object/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/public-object/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/public-object/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/public-object/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/public-object/tests/Core/Client/RawClientTest.php b/seed/php-sdk/public-object/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/public-object/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/public-object/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/query-param-name-conflict/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/query-param-name-conflict/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/query-param-name-conflict/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/query-param-name-conflict/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/query-param-name-conflict/tests/Core/Client/RawClientTest.php b/seed/php-sdk/query-param-name-conflict/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/query-param-name-conflict/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/query-param-name-conflict/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/query-parameters-openapi-as-objects/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/query-parameters-openapi-as-objects/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/query-parameters-openapi-as-objects/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/query-parameters-openapi-as-objects/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/query-parameters-openapi-as-objects/tests/Core/Client/RawClientTest.php b/seed/php-sdk/query-parameters-openapi-as-objects/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/query-parameters-openapi-as-objects/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/query-parameters-openapi-as-objects/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/query-parameters-openapi/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/query-parameters-openapi/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/query-parameters-openapi/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/query-parameters-openapi/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/query-parameters-openapi/tests/Core/Client/RawClientTest.php b/seed/php-sdk/query-parameters-openapi/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/query-parameters-openapi/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/query-parameters-openapi/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/query-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/query-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/query-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/query-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/query-parameters/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/query-parameters/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/query-parameters/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/query-parameters/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/query-parameters/private/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/query-parameters/private/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/query-parameters/private/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/query-parameters/private/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/query-parameters/private/tests/Core/Client/RawClientTest.php b/seed/php-sdk/query-parameters/private/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/query-parameters/private/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/query-parameters/private/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/request-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/request-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/request-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/request-parameters/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/request-parameters/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/request-parameters/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/request-parameters/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/request-parameters/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/request-parameters/with-defaults/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/request-parameters/with-defaults/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/request-parameters/with-defaults/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/request-parameters/with-defaults/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/request-parameters/with-defaults/tests/Core/Client/RawClientTest.php b/seed/php-sdk/request-parameters/with-defaults/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/request-parameters/with-defaults/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/request-parameters/with-defaults/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/required-nullable/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/required-nullable/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/required-nullable/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/required-nullable/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/required-nullable/tests/Core/Client/RawClientTest.php b/seed/php-sdk/required-nullable/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/required-nullable/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/required-nullable/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/reserved-keywords/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/reserved-keywords/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/reserved-keywords/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/reserved-keywords/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/reserved-keywords/tests/Core/Client/RawClientTest.php b/seed/php-sdk/reserved-keywords/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/reserved-keywords/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/reserved-keywords/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/response-property/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/response-property/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/response-property/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/response-property/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/response-property/tests/Core/Client/RawClientTest.php b/seed/php-sdk/response-property/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/response-property/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/response-property/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/schemaless-request-body-examples/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/schemaless-request-body-examples/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/schemaless-request-body-examples/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/schemaless-request-body-examples/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/schemaless-request-body-examples/tests/Core/Client/RawClientTest.php b/seed/php-sdk/schemaless-request-body-examples/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/schemaless-request-body-examples/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/schemaless-request-body-examples/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/seed.yml b/seed/php-sdk/seed.yml index b4916f6de66d..6629f2726080 100644 --- a/seed/php-sdk/seed.yml +++ b/seed/php-sdk/seed.yml @@ -182,6 +182,7 @@ scripts: test: - composer test allowedFailures: + - api-wide-base-path-with-default - exhaustive:no-custom-config - exhaustive:wire-tests - pagination-custom diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/server-sent-event-examples/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/server-sent-event-examples/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/server-sent-event-examples/tests/Core/Client/RawClientTest.php b/seed/php-sdk/server-sent-event-examples/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/server-sent-event-examples/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/server-sent-event-examples/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/server-sent-events-openapi/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/server-sent-events-openapi/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/server-sent-events-openapi/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/server-sent-events-openapi/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/server-sent-events-openapi/tests/Core/Client/RawClientTest.php b/seed/php-sdk/server-sent-events-openapi/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/server-sent-events-openapi/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/server-sent-events-openapi/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/server-sent-events-resumable/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/server-sent-events-resumable/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/server-sent-events-resumable/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/server-sent-events-resumable/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/server-sent-events-resumable/tests/Core/Client/RawClientTest.php b/seed/php-sdk/server-sent-events-resumable/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/server-sent-events-resumable/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/server-sent-events-resumable/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/server-sent-events/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/server-sent-events/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/server-sent-events/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/server-sent-events/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/server-sent-events/tests/Core/Client/RawClientTest.php b/seed/php-sdk/server-sent-events/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/server-sent-events/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/server-sent-events/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/server-url-templating/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/server-url-templating/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/server-url-templating/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/server-url-templating/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/server-url-templating/tests/Core/Client/RawClientTest.php b/seed/php-sdk/server-url-templating/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/server-url-templating/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/server-url-templating/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/simple-api/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/simple-api/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/simple-api/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/simple-api/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/simple-api/tests/Core/Client/RawClientTest.php b/seed/php-sdk/simple-api/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/simple-api/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/simple-api/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/simple-fhir/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/simple-fhir/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/simple-fhir/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/simple-fhir/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/simple-fhir/tests/Core/Client/RawClientTest.php b/seed/php-sdk/simple-fhir/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/simple-fhir/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/simple-fhir/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/single-url-environment-default/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/single-url-environment-default/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/single-url-environment-default/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/single-url-environment-default/tests/Core/Client/RawClientTest.php b/seed/php-sdk/single-url-environment-default/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/single-url-environment-default/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/single-url-environment-default/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Core/Client/RawClientTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/single-url-environment-no-default/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/single-url-environment-no-default/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/streaming-parameter/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/streaming-parameter/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/streaming-parameter/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/streaming-parameter/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/streaming-parameter/tests/Core/Client/RawClientTest.php b/seed/php-sdk/streaming-parameter/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/streaming-parameter/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/streaming-parameter/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/streaming/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/streaming/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/streaming/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/streaming/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/streaming/tests/Core/Client/RawClientTest.php b/seed/php-sdk/streaming/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/streaming/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/streaming/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/trace/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/trace/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/trace/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/trace/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/trace/tests/Core/Client/RawClientTest.php b/seed/php-sdk/trace/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/trace/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/trace/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/undiscriminated-union-with-response-property/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/undiscriminated-union-with-response-property/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/undiscriminated-union-with-response-property/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/undiscriminated-union-with-response-property/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/undiscriminated-union-with-response-property/tests/Core/Client/RawClientTest.php b/seed/php-sdk/undiscriminated-union-with-response-property/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/undiscriminated-union-with-response-property/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/undiscriminated-union-with-response-property/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/undiscriminated-unions/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/undiscriminated-unions/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/undiscriminated-unions/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/undiscriminated-unions/tests/Core/Client/RawClientTest.php b/seed/php-sdk/undiscriminated-unions/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/undiscriminated-unions/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/undiscriminated-unions/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/union-query-parameters/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/union-query-parameters/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/union-query-parameters/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/union-query-parameters/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/union-query-parameters/tests/Core/Client/RawClientTest.php b/seed/php-sdk/union-query-parameters/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/union-query-parameters/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/union-query-parameters/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/unions-with-local-date/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/unions-with-local-date/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/unions-with-local-date/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/unions-with-local-date/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/unions-with-local-date/tests/Core/Client/RawClientTest.php b/seed/php-sdk/unions-with-local-date/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/unions-with-local-date/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/unions-with-local-date/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/unions/no-custom-config/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/unions/no-custom-config/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/unions/no-custom-config/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/unions/no-custom-config/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/unions/no-custom-config/tests/Core/Client/RawClientTest.php b/seed/php-sdk/unions/no-custom-config/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/unions/no-custom-config/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/unions/no-custom-config/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/unions/property-accessors/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/unions/property-accessors/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/unions/property-accessors/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/unions/property-accessors/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/unions/property-accessors/tests/Core/Client/RawClientTest.php b/seed/php-sdk/unions/property-accessors/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/unions/property-accessors/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/unions/property-accessors/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/unknown/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/unknown/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/unknown/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/unknown/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/unknown/tests/Core/Client/RawClientTest.php b/seed/php-sdk/unknown/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/unknown/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/unknown/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/url-form-encoded/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/url-form-encoded/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/url-form-encoded/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/url-form-encoded/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/url-form-encoded/tests/Core/Client/RawClientTest.php b/seed/php-sdk/url-form-encoded/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/url-form-encoded/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/url-form-encoded/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/validation/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/validation/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/validation/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/validation/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/validation/tests/Core/Client/RawClientTest.php b/seed/php-sdk/validation/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/validation/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/validation/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/variables/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/variables/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/variables/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/variables/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/variables/tests/Core/Client/RawClientTest.php b/seed/php-sdk/variables/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/variables/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/variables/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/version-no-default/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/version-no-default/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/version-no-default/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/version-no-default/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/version-no-default/tests/Core/Client/RawClientTest.php b/seed/php-sdk/version-no-default/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/version-no-default/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/version-no-default/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/version/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/version/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/version/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/version/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/version/tests/Core/Client/RawClientTest.php b/seed/php-sdk/version/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/version/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/version/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/webhook-audience/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/webhook-audience/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/webhook-audience/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/webhook-audience/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/webhook-audience/tests/Core/Client/RawClientTest.php b/seed/php-sdk/webhook-audience/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/webhook-audience/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/webhook-audience/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/webhooks/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/webhooks/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/webhooks/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/webhooks/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/webhooks/tests/Core/Client/RawClientTest.php b/seed/php-sdk/webhooks/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/webhooks/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/webhooks/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/websocket-bearer-auth/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/websocket-bearer-auth/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/websocket-bearer-auth/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/websocket-bearer-auth/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/websocket-bearer-auth/tests/Core/Client/RawClientTest.php b/seed/php-sdk/websocket-bearer-auth/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/websocket-bearer-auth/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/websocket-bearer-auth/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/websocket-inferred-auth/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/websocket-inferred-auth/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/websocket-inferred-auth/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/websocket-inferred-auth/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/websocket-inferred-auth/tests/Core/Client/RawClientTest.php b/seed/php-sdk/websocket-inferred-auth/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/websocket-inferred-auth/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/websocket-inferred-auth/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/websocket-multi-url/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/websocket-multi-url/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/websocket-multi-url/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/websocket-multi-url/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/websocket-multi-url/tests/Core/Client/RawClientTest.php b/seed/php-sdk/websocket-multi-url/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/websocket-multi-url/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/websocket-multi-url/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/websocket/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/websocket/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/websocket/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/websocket/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/websocket/tests/Core/Client/RawClientTest.php b/seed/php-sdk/websocket/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/websocket/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/websocket/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/php-sdk/x-fern-default/src/Core/Client/RetryDecoratingClient.php b/seed/php-sdk/x-fern-default/src/Core/Client/RetryDecoratingClient.php index b16170cf2805..46d82cb552fb 100644 --- a/seed/php-sdk/x-fern-default/src/Core/Client/RetryDecoratingClient.php +++ b/seed/php-sdk/x-fern-default/src/Core/Client/RetryDecoratingClient.php @@ -108,7 +108,7 @@ private function doSend(RequestInterface $request, ?float $timeout): ResponseInt return $this->client->sendRequest($request); } - if (class_exists('GuzzleHttp\ClientInterface') + if (interface_exists('GuzzleHttp\ClientInterface') && $this->client instanceof \GuzzleHttp\ClientInterface ) { return $this->client->send($request, ['timeout' => $timeout]); diff --git a/seed/php-sdk/x-fern-default/tests/Core/Client/RawClientTest.php b/seed/php-sdk/x-fern-default/tests/Core/Client/RawClientTest.php index df36dc918894..cb63148311b5 100644 --- a/seed/php-sdk/x-fern-default/tests/Core/Client/RawClientTest.php +++ b/seed/php-sdk/x-fern-default/tests/Core/Client/RawClientTest.php @@ -1071,4 +1071,145 @@ public function testDiscoveryFindsFactories(): void $stream = $streamFactory->createStream('hello'); $this->assertEquals('hello', (string) $stream); } + + public function testInterfaceExistsDetectsGuzzle(): void + { + $this->assertTrue( + interface_exists('GuzzleHttp\ClientInterface'), + 'interface_exists should detect GuzzleHttp\ClientInterface when Guzzle is installed', + ); + } + + public function testTimeoutForwardsToGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + /** @var array */ + public array $lastOptions = []; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->lastOptions = $options; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: 5.0); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('timeout', $guzzleClient->lastOptions); + $this->assertEquals(5.0, $guzzleClient->lastOptions['timeout']); + } + + public function testNoTimeoutDoesNotCallGuzzleSend(): void + { + $expectedResponse = self::createResponse(200); + + $guzzleClient = new class ($expectedResponse) implements \Psr\Http\Client\ClientInterface, \GuzzleHttp\ClientInterface { + private ResponseInterface $response; + public bool $sendCalled = false; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } + + /** @param array $options */ + public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): ResponseInterface + { + $this->sendCalled = true; + return $this->response; + } + + /** @param array $options */ + public function sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function request(string $method, $uri, array $options = []): ResponseInterface + { + throw new \RuntimeException('Not implemented'); + } + + /** @param array $options */ + public function requestAsync(string $method, $uri, array $options = []): \GuzzleHttp\Promise\PromiseInterface + { + throw new \RuntimeException('Not implemented'); + } + + public function getConfig(?string $option = null) + { + return null; + } + + public function sendRequest(\Psr\Http\Message\RequestInterface $request): ResponseInterface + { + return $this->response; + } + }; + + $retryClient = new RetryDecoratingClient( + $guzzleClient, + maxRetries: 0, + sleepFunction: function (int $_microseconds): void { + }, + ); + + $requestFactory = \Http\Discovery\Psr17FactoryDiscovery::findRequestFactory(); + $request = $requestFactory->createRequest('GET', $this->baseUrl . '/test'); + + $response = $retryClient->send($request, timeout: null); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($guzzleClient->sendCalled, 'Guzzle send() should not be called when timeout is null'); + } + } diff --git a/seed/python-sdk/accept-header/poetry.lock b/seed/python-sdk/accept-header/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/accept-header/poetry.lock +++ b/seed/python-sdk/accept-header/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/alias-extends/no-custom-config/poetry.lock b/seed/python-sdk/alias-extends/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/alias-extends/no-custom-config/poetry.lock +++ b/seed/python-sdk/alias-extends/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/alias-extends/no-inheritance-for-extended-models/poetry.lock b/seed/python-sdk/alias-extends/no-inheritance-for-extended-models/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/alias-extends/no-inheritance-for-extended-models/poetry.lock +++ b/seed/python-sdk/alias-extends/no-inheritance-for-extended-models/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/alias/poetry.lock b/seed/python-sdk/alias/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/alias/poetry.lock +++ b/seed/python-sdk/alias/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/allof-inline/no-custom-config/poetry.lock b/seed/python-sdk/allof-inline/no-custom-config/poetry.lock index 53515816316b..9185b8246815 100644 --- a/seed/python-sdk/allof-inline/no-custom-config/poetry.lock +++ b/seed/python-sdk/allof-inline/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/allof/no-custom-config/poetry.lock b/seed/python-sdk/allof/no-custom-config/poetry.lock index 53515816316b..9185b8246815 100644 --- a/seed/python-sdk/allof/no-custom-config/poetry.lock +++ b/seed/python-sdk/allof/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/any-auth/poetry.lock b/seed/python-sdk/any-auth/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/any-auth/poetry.lock +++ b/seed/python-sdk/any-auth/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/api-wide-base-path-with-default/poetry.lock b/seed/python-sdk/api-wide-base-path-with-default/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/api-wide-base-path-with-default/poetry.lock +++ b/seed/python-sdk/api-wide-base-path-with-default/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/api-wide-base-path/poetry.lock b/seed/python-sdk/api-wide-base-path/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/api-wide-base-path/poetry.lock +++ b/seed/python-sdk/api-wide-base-path/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/audiences/poetry.lock b/seed/python-sdk/audiences/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/audiences/poetry.lock +++ b/seed/python-sdk/audiences/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/basic-auth-environment-variables/poetry.lock b/seed/python-sdk/basic-auth-environment-variables/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/basic-auth-environment-variables/poetry.lock +++ b/seed/python-sdk/basic-auth-environment-variables/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/basic-auth-pw-omitted/with-wire-tests/poetry.lock b/seed/python-sdk/basic-auth-pw-omitted/with-wire-tests/poetry.lock index 53515816316b..9185b8246815 100644 --- a/seed/python-sdk/basic-auth-pw-omitted/with-wire-tests/poetry.lock +++ b/seed/python-sdk/basic-auth-pw-omitted/with-wire-tests/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/basic-auth/poetry.lock b/seed/python-sdk/basic-auth/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/basic-auth/poetry.lock +++ b/seed/python-sdk/basic-auth/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/bearer-token-environment-variable/poetry.lock b/seed/python-sdk/bearer-token-environment-variable/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/bearer-token-environment-variable/poetry.lock +++ b/seed/python-sdk/bearer-token-environment-variable/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/bytes-download/poetry.lock b/seed/python-sdk/bytes-download/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/bytes-download/poetry.lock +++ b/seed/python-sdk/bytes-download/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/bytes-upload/poetry.lock b/seed/python-sdk/bytes-upload/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/bytes-upload/poetry.lock +++ b/seed/python-sdk/bytes-upload/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/circular-references-advanced/no-inheritance-for-extended-models/poetry.lock b/seed/python-sdk/circular-references-advanced/no-inheritance-for-extended-models/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/circular-references-advanced/no-inheritance-for-extended-models/poetry.lock +++ b/seed/python-sdk/circular-references-advanced/no-inheritance-for-extended-models/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/circular-references-extends/no-custom-config/poetry.lock b/seed/python-sdk/circular-references-extends/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/circular-references-extends/no-custom-config/poetry.lock +++ b/seed/python-sdk/circular-references-extends/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/circular-references/no-custom-config/poetry.lock b/seed/python-sdk/circular-references/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/circular-references/no-custom-config/poetry.lock +++ b/seed/python-sdk/circular-references/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/circular-references/no-inheritance-for-extended-models/poetry.lock b/seed/python-sdk/circular-references/no-inheritance-for-extended-models/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/circular-references/no-inheritance-for-extended-models/poetry.lock +++ b/seed/python-sdk/circular-references/no-inheritance-for-extended-models/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/client-side-params/poetry.lock b/seed/python-sdk/client-side-params/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/client-side-params/poetry.lock +++ b/seed/python-sdk/client-side-params/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/content-type/poetry.lock b/seed/python-sdk/content-type/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/content-type/poetry.lock +++ b/seed/python-sdk/content-type/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/cross-package-type-names/poetry.lock b/seed/python-sdk/cross-package-type-names/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/cross-package-type-names/poetry.lock +++ b/seed/python-sdk/cross-package-type-names/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/dollar-string-examples/poetry.lock b/seed/python-sdk/dollar-string-examples/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/dollar-string-examples/poetry.lock +++ b/seed/python-sdk/dollar-string-examples/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/empty-clients/poetry.lock b/seed/python-sdk/empty-clients/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/empty-clients/poetry.lock +++ b/seed/python-sdk/empty-clients/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/endpoint-security-auth/poetry.lock b/seed/python-sdk/endpoint-security-auth/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/endpoint-security-auth/poetry.lock +++ b/seed/python-sdk/endpoint-security-auth/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/enum/no-custom-config/poetry.lock b/seed/python-sdk/enum/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/enum/no-custom-config/poetry.lock +++ b/seed/python-sdk/enum/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/enum/real-enum-forward-compat/poetry.lock b/seed/python-sdk/enum/real-enum-forward-compat/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/enum/real-enum-forward-compat/poetry.lock +++ b/seed/python-sdk/enum/real-enum-forward-compat/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/enum/real-enum/poetry.lock b/seed/python-sdk/enum/real-enum/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/enum/real-enum/poetry.lock +++ b/seed/python-sdk/enum/real-enum/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/enum/strenum/poetry.lock b/seed/python-sdk/enum/strenum/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/enum/strenum/poetry.lock +++ b/seed/python-sdk/enum/strenum/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/error-property/poetry.lock b/seed/python-sdk/error-property/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/error-property/poetry.lock +++ b/seed/python-sdk/error-property/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/errors/poetry.lock b/seed/python-sdk/errors/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/errors/poetry.lock +++ b/seed/python-sdk/errors/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/examples/additional_init_exports_with_duplicates/poetry.lock b/seed/python-sdk/examples/additional_init_exports_with_duplicates/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/examples/additional_init_exports_with_duplicates/poetry.lock +++ b/seed/python-sdk/examples/additional_init_exports_with_duplicates/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/examples/client-filename/poetry.lock b/seed/python-sdk/examples/client-filename/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/examples/client-filename/poetry.lock +++ b/seed/python-sdk/examples/client-filename/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/examples/legacy-wire-tests/poetry.lock b/seed/python-sdk/examples/legacy-wire-tests/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/examples/legacy-wire-tests/poetry.lock +++ b/seed/python-sdk/examples/legacy-wire-tests/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/examples/no-custom-config/poetry.lock b/seed/python-sdk/examples/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/examples/no-custom-config/poetry.lock +++ b/seed/python-sdk/examples/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/examples/omit-fern-headers/poetry.lock b/seed/python-sdk/examples/omit-fern-headers/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/examples/omit-fern-headers/poetry.lock +++ b/seed/python-sdk/examples/omit-fern-headers/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/examples/readme/poetry.lock b/seed/python-sdk/examples/readme/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/examples/readme/poetry.lock +++ b/seed/python-sdk/examples/readme/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/additional_init_exports/poetry.lock b/seed/python-sdk/exhaustive/additional_init_exports/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/additional_init_exports/poetry.lock +++ b/seed/python-sdk/exhaustive/additional_init_exports/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/aliases_with_validation/poetry.lock b/seed/python-sdk/exhaustive/aliases_with_validation/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/aliases_with_validation/poetry.lock +++ b/seed/python-sdk/exhaustive/aliases_with_validation/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/aliases_without_validation/poetry.lock b/seed/python-sdk/exhaustive/aliases_without_validation/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/aliases_without_validation/poetry.lock +++ b/seed/python-sdk/exhaustive/aliases_without_validation/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/custom-transport/poetry.lock b/seed/python-sdk/exhaustive/custom-transport/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/custom-transport/poetry.lock +++ b/seed/python-sdk/exhaustive/custom-transport/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/datetime-milliseconds/poetry.lock b/seed/python-sdk/exhaustive/datetime-milliseconds/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/datetime-milliseconds/poetry.lock +++ b/seed/python-sdk/exhaustive/datetime-milliseconds/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/deps_with_min_python_version/poetry.lock b/seed/python-sdk/exhaustive/deps_with_min_python_version/poetry.lock index c91e8618de05..6dc90a09be47 100644 --- a/seed/python-sdk/exhaustive/deps_with_min_python_version/poetry.lock +++ b/seed/python-sdk/exhaustive/deps_with_min_python_version/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -287,14 +287,14 @@ crt = ["awscrt (==0.22.0)"] [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/eager-imports/poetry.lock b/seed/python-sdk/exhaustive/eager-imports/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/eager-imports/poetry.lock +++ b/seed/python-sdk/exhaustive/eager-imports/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/extra_dependencies/poetry.lock b/seed/python-sdk/exhaustive/extra_dependencies/poetry.lock index da6595561201..7796e288b290 100644 --- a/seed/python-sdk/exhaustive/extra_dependencies/poetry.lock +++ b/seed/python-sdk/exhaustive/extra_dependencies/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -289,14 +289,14 @@ crt = ["awscrt (==0.22.0)"] [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/extra_dev_dependencies/poetry.lock b/seed/python-sdk/exhaustive/extra_dev_dependencies/poetry.lock index 6bfc0e560759..2758a8144416 100644 --- a/seed/python-sdk/exhaustive/extra_dev_dependencies/poetry.lock +++ b/seed/python-sdk/exhaustive/extra_dev_dependencies/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -287,14 +287,14 @@ crt = ["awscrt (==0.22.0)"] [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/five-second-timeout/poetry.lock b/seed/python-sdk/exhaustive/five-second-timeout/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/five-second-timeout/poetry.lock +++ b/seed/python-sdk/exhaustive/five-second-timeout/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/follow_redirects_by_default/poetry.lock b/seed/python-sdk/exhaustive/follow_redirects_by_default/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/follow_redirects_by_default/poetry.lock +++ b/seed/python-sdk/exhaustive/follow_redirects_by_default/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/import-paths/poetry.lock b/seed/python-sdk/exhaustive/import-paths/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/import-paths/poetry.lock +++ b/seed/python-sdk/exhaustive/import-paths/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/improved_imports/poetry.lock b/seed/python-sdk/exhaustive/improved_imports/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/improved_imports/poetry.lock +++ b/seed/python-sdk/exhaustive/improved_imports/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/infinite-timeout/poetry.lock b/seed/python-sdk/exhaustive/infinite-timeout/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/infinite-timeout/poetry.lock +++ b/seed/python-sdk/exhaustive/infinite-timeout/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/inline-path-params/poetry.lock b/seed/python-sdk/exhaustive/inline-path-params/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/inline-path-params/poetry.lock +++ b/seed/python-sdk/exhaustive/inline-path-params/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/inline_request_params/poetry.lock b/seed/python-sdk/exhaustive/inline_request_params/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/inline_request_params/poetry.lock +++ b/seed/python-sdk/exhaustive/inline_request_params/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/no-custom-config/poetry.lock b/seed/python-sdk/exhaustive/no-custom-config/poetry.lock index 53515816316b..9185b8246815 100644 --- a/seed/python-sdk/exhaustive/no-custom-config/poetry.lock +++ b/seed/python-sdk/exhaustive/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/output-directory-project-root/poetry.lock b/seed/python-sdk/exhaustive/output-directory-project-root/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/output-directory-project-root/poetry.lock +++ b/seed/python-sdk/exhaustive/output-directory-project-root/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/package-path/poetry.lock b/seed/python-sdk/exhaustive/package-path/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/package-path/poetry.lock +++ b/seed/python-sdk/exhaustive/package-path/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/pydantic-extra-fields/poetry.lock b/seed/python-sdk/exhaustive/pydantic-extra-fields/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/pydantic-extra-fields/poetry.lock +++ b/seed/python-sdk/exhaustive/pydantic-extra-fields/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/pydantic-ignore-fields/poetry.lock b/seed/python-sdk/exhaustive/pydantic-ignore-fields/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/pydantic-ignore-fields/poetry.lock +++ b/seed/python-sdk/exhaustive/pydantic-ignore-fields/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/pydantic-v1-with-utils/poetry.lock b/seed/python-sdk/exhaustive/pydantic-v1-with-utils/poetry.lock index 76741d152574..d95420bece79 100644 --- a/seed/python-sdk/exhaustive/pydantic-v1-with-utils/poetry.lock +++ b/seed/python-sdk/exhaustive/pydantic-v1-with-utils/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -235,14 +235,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/pydantic-v1-wrapped/poetry.lock b/seed/python-sdk/exhaustive/pydantic-v1-wrapped/poetry.lock index 76741d152574..d95420bece79 100644 --- a/seed/python-sdk/exhaustive/pydantic-v1-wrapped/poetry.lock +++ b/seed/python-sdk/exhaustive/pydantic-v1-wrapped/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -235,14 +235,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/pydantic-v1/poetry.lock b/seed/python-sdk/exhaustive/pydantic-v1/poetry.lock index 76741d152574..d95420bece79 100644 --- a/seed/python-sdk/exhaustive/pydantic-v1/poetry.lock +++ b/seed/python-sdk/exhaustive/pydantic-v1/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -235,14 +235,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/pydantic-v2-wrapped/poetry.lock b/seed/python-sdk/exhaustive/pydantic-v2-wrapped/poetry.lock index 68cccc894038..179daa7e46f9 100644 --- a/seed/python-sdk/exhaustive/pydantic-v2-wrapped/poetry.lock +++ b/seed/python-sdk/exhaustive/pydantic-v2-wrapped/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/pyproject_extras/poetry.lock b/seed/python-sdk/exhaustive/pyproject_extras/poetry.lock index 57d51aaf0642..269d81779fb7 100644 --- a/seed/python-sdk/exhaustive/pyproject_extras/poetry.lock +++ b/seed/python-sdk/exhaustive/pyproject_extras/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/skip-pydantic-validation/poetry.lock b/seed/python-sdk/exhaustive/skip-pydantic-validation/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/skip-pydantic-validation/poetry.lock +++ b/seed/python-sdk/exhaustive/skip-pydantic-validation/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/union-utils/poetry.lock b/seed/python-sdk/exhaustive/union-utils/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/exhaustive/union-utils/poetry.lock +++ b/seed/python-sdk/exhaustive/union-utils/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/exhaustive/wire-tests-custom-client-name/poetry.lock b/seed/python-sdk/exhaustive/wire-tests-custom-client-name/poetry.lock index 53515816316b..9185b8246815 100644 --- a/seed/python-sdk/exhaustive/wire-tests-custom-client-name/poetry.lock +++ b/seed/python-sdk/exhaustive/wire-tests-custom-client-name/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/extends/poetry.lock b/seed/python-sdk/extends/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/extends/poetry.lock +++ b/seed/python-sdk/extends/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/extra-properties/poetry.lock b/seed/python-sdk/extra-properties/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/extra-properties/poetry.lock +++ b/seed/python-sdk/extra-properties/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/file-download/default-chunk-size/poetry.lock b/seed/python-sdk/file-download/default-chunk-size/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/file-download/default-chunk-size/poetry.lock +++ b/seed/python-sdk/file-download/default-chunk-size/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/file-download/no-custom-config/poetry.lock b/seed/python-sdk/file-download/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/file-download/no-custom-config/poetry.lock +++ b/seed/python-sdk/file-download/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/file-upload-openapi/poetry.lock b/seed/python-sdk/file-upload-openapi/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/file-upload-openapi/poetry.lock +++ b/seed/python-sdk/file-upload-openapi/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/file-upload/exclude_types_from_init_exports/poetry.lock b/seed/python-sdk/file-upload/exclude_types_from_init_exports/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/file-upload/exclude_types_from_init_exports/poetry.lock +++ b/seed/python-sdk/file-upload/exclude_types_from_init_exports/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/file-upload/no-custom-config/poetry.lock b/seed/python-sdk/file-upload/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/file-upload/no-custom-config/poetry.lock +++ b/seed/python-sdk/file-upload/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/file-upload/use_typeddict_requests/poetry.lock b/seed/python-sdk/file-upload/use_typeddict_requests/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/file-upload/use_typeddict_requests/poetry.lock +++ b/seed/python-sdk/file-upload/use_typeddict_requests/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/folders/poetry.lock b/seed/python-sdk/folders/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/folders/poetry.lock +++ b/seed/python-sdk/folders/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/header-auth-environment-variable/poetry.lock b/seed/python-sdk/header-auth-environment-variable/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/header-auth-environment-variable/poetry.lock +++ b/seed/python-sdk/header-auth-environment-variable/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/header-auth/poetry.lock b/seed/python-sdk/header-auth/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/header-auth/poetry.lock +++ b/seed/python-sdk/header-auth/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/http-head/poetry.lock b/seed/python-sdk/http-head/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/http-head/poetry.lock +++ b/seed/python-sdk/http-head/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/idempotency-headers/poetry.lock b/seed/python-sdk/idempotency-headers/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/idempotency-headers/poetry.lock +++ b/seed/python-sdk/idempotency-headers/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/imdb/poetry.lock b/seed/python-sdk/imdb/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/imdb/poetry.lock +++ b/seed/python-sdk/imdb/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/inferred-auth-explicit/poetry.lock b/seed/python-sdk/inferred-auth-explicit/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/inferred-auth-explicit/poetry.lock +++ b/seed/python-sdk/inferred-auth-explicit/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/inferred-auth-implicit-api-key/poetry.lock b/seed/python-sdk/inferred-auth-implicit-api-key/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/inferred-auth-implicit-api-key/poetry.lock +++ b/seed/python-sdk/inferred-auth-implicit-api-key/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/inferred-auth-implicit-no-expiry/poetry.lock b/seed/python-sdk/inferred-auth-implicit-no-expiry/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/inferred-auth-implicit-no-expiry/poetry.lock +++ b/seed/python-sdk/inferred-auth-implicit-no-expiry/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/inferred-auth-implicit-reference/poetry.lock b/seed/python-sdk/inferred-auth-implicit-reference/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/inferred-auth-implicit-reference/poetry.lock +++ b/seed/python-sdk/inferred-auth-implicit-reference/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/inferred-auth-implicit/poetry.lock b/seed/python-sdk/inferred-auth-implicit/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/inferred-auth-implicit/poetry.lock +++ b/seed/python-sdk/inferred-auth-implicit/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/license/poetry.lock b/seed/python-sdk/license/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/license/poetry.lock +++ b/seed/python-sdk/license/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/literal-user-agent/no-custom-config/poetry.lock b/seed/python-sdk/literal-user-agent/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/literal-user-agent/no-custom-config/poetry.lock +++ b/seed/python-sdk/literal-user-agent/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/literal/no-custom-config/poetry.lock b/seed/python-sdk/literal/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/literal/no-custom-config/poetry.lock +++ b/seed/python-sdk/literal/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/literal/use_typeddict_requests/poetry.lock b/seed/python-sdk/literal/use_typeddict_requests/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/literal/use_typeddict_requests/poetry.lock +++ b/seed/python-sdk/literal/use_typeddict_requests/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/literals-unions/poetry.lock b/seed/python-sdk/literals-unions/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/literals-unions/poetry.lock +++ b/seed/python-sdk/literals-unions/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/mixed-case/poetry.lock b/seed/python-sdk/mixed-case/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/mixed-case/poetry.lock +++ b/seed/python-sdk/mixed-case/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/mixed-file-directory/exclude_types_from_init_exports/poetry.lock b/seed/python-sdk/mixed-file-directory/exclude_types_from_init_exports/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/mixed-file-directory/exclude_types_from_init_exports/poetry.lock +++ b/seed/python-sdk/mixed-file-directory/exclude_types_from_init_exports/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/mixed-file-directory/no-custom-config/poetry.lock b/seed/python-sdk/mixed-file-directory/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/mixed-file-directory/no-custom-config/poetry.lock +++ b/seed/python-sdk/mixed-file-directory/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/multi-line-docs/poetry.lock b/seed/python-sdk/multi-line-docs/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/multi-line-docs/poetry.lock +++ b/seed/python-sdk/multi-line-docs/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/multi-url-environment-no-default/poetry.lock b/seed/python-sdk/multi-url-environment-no-default/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/multi-url-environment-no-default/poetry.lock +++ b/seed/python-sdk/multi-url-environment-no-default/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/multi-url-environment-reference/poetry.lock b/seed/python-sdk/multi-url-environment-reference/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/multi-url-environment-reference/poetry.lock +++ b/seed/python-sdk/multi-url-environment-reference/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/multi-url-environment/poetry.lock b/seed/python-sdk/multi-url-environment/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/multi-url-environment/poetry.lock +++ b/seed/python-sdk/multi-url-environment/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/multiple-request-bodies/poetry.lock b/seed/python-sdk/multiple-request-bodies/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/multiple-request-bodies/poetry.lock +++ b/seed/python-sdk/multiple-request-bodies/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/no-content-response/poetry.lock b/seed/python-sdk/no-content-response/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/no-content-response/poetry.lock +++ b/seed/python-sdk/no-content-response/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/no-environment/poetry.lock b/seed/python-sdk/no-environment/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/no-environment/poetry.lock +++ b/seed/python-sdk/no-environment/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/no-retries/poetry.lock b/seed/python-sdk/no-retries/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/no-retries/poetry.lock +++ b/seed/python-sdk/no-retries/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/null-type/poetry.lock b/seed/python-sdk/null-type/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/null-type/poetry.lock +++ b/seed/python-sdk/null-type/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/nullable-allof-extends/poetry.lock b/seed/python-sdk/nullable-allof-extends/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/nullable-allof-extends/poetry.lock +++ b/seed/python-sdk/nullable-allof-extends/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/nullable-optional/poetry.lock b/seed/python-sdk/nullable-optional/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/nullable-optional/poetry.lock +++ b/seed/python-sdk/nullable-optional/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/nullable-request-body/poetry.lock b/seed/python-sdk/nullable-request-body/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/nullable-request-body/poetry.lock +++ b/seed/python-sdk/nullable-request-body/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/nullable/no-custom-config/poetry.lock b/seed/python-sdk/nullable/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/nullable/no-custom-config/poetry.lock +++ b/seed/python-sdk/nullable/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/nullable/use-typeddict-requests/poetry.lock b/seed/python-sdk/nullable/use-typeddict-requests/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/nullable/use-typeddict-requests/poetry.lock +++ b/seed/python-sdk/nullable/use-typeddict-requests/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/oauth-client-credentials-custom/poetry.lock b/seed/python-sdk/oauth-client-credentials-custom/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/oauth-client-credentials-custom/poetry.lock +++ b/seed/python-sdk/oauth-client-credentials-custom/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/oauth-client-credentials-default/poetry.lock b/seed/python-sdk/oauth-client-credentials-default/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/oauth-client-credentials-default/poetry.lock +++ b/seed/python-sdk/oauth-client-credentials-default/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/oauth-client-credentials-environment-variables/poetry.lock b/seed/python-sdk/oauth-client-credentials-environment-variables/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/oauth-client-credentials-environment-variables/poetry.lock +++ b/seed/python-sdk/oauth-client-credentials-environment-variables/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/poetry.lock b/seed/python-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/poetry.lock +++ b/seed/python-sdk/oauth-client-credentials-mandatory-auth/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/oauth-client-credentials-nested-root/poetry.lock b/seed/python-sdk/oauth-client-credentials-nested-root/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/oauth-client-credentials-nested-root/poetry.lock +++ b/seed/python-sdk/oauth-client-credentials-nested-root/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/oauth-client-credentials-openapi/poetry.lock b/seed/python-sdk/oauth-client-credentials-openapi/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/oauth-client-credentials-openapi/poetry.lock +++ b/seed/python-sdk/oauth-client-credentials-openapi/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/oauth-client-credentials-reference/poetry.lock b/seed/python-sdk/oauth-client-credentials-reference/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/oauth-client-credentials-reference/poetry.lock +++ b/seed/python-sdk/oauth-client-credentials-reference/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/oauth-client-credentials-with-variables/poetry.lock b/seed/python-sdk/oauth-client-credentials-with-variables/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/oauth-client-credentials-with-variables/poetry.lock +++ b/seed/python-sdk/oauth-client-credentials-with-variables/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/oauth-client-credentials/poetry.lock b/seed/python-sdk/oauth-client-credentials/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/oauth-client-credentials/poetry.lock +++ b/seed/python-sdk/oauth-client-credentials/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/object/poetry.lock b/seed/python-sdk/object/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/object/poetry.lock +++ b/seed/python-sdk/object/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/objects-with-imports/poetry.lock b/seed/python-sdk/objects-with-imports/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/objects-with-imports/poetry.lock +++ b/seed/python-sdk/objects-with-imports/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/openapi-request-body-ref/poetry.lock b/seed/python-sdk/openapi-request-body-ref/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/openapi-request-body-ref/poetry.lock +++ b/seed/python-sdk/openapi-request-body-ref/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/optional/poetry.lock b/seed/python-sdk/optional/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/optional/poetry.lock +++ b/seed/python-sdk/optional/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/package-yml/poetry.lock b/seed/python-sdk/package-yml/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/package-yml/poetry.lock +++ b/seed/python-sdk/package-yml/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/pagination-custom/poetry.lock b/seed/python-sdk/pagination-custom/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/pagination-custom/poetry.lock +++ b/seed/python-sdk/pagination-custom/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/pagination-uri-path/poetry.lock b/seed/python-sdk/pagination-uri-path/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/pagination-uri-path/poetry.lock +++ b/seed/python-sdk/pagination-uri-path/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/pagination/no-custom-config/poetry.lock b/seed/python-sdk/pagination/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/pagination/no-custom-config/poetry.lock +++ b/seed/python-sdk/pagination/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/pagination/no-inheritance-for-extended-models/poetry.lock b/seed/python-sdk/pagination/no-inheritance-for-extended-models/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/pagination/no-inheritance-for-extended-models/poetry.lock +++ b/seed/python-sdk/pagination/no-inheritance-for-extended-models/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/pagination/page-index-semantics/poetry.lock b/seed/python-sdk/pagination/page-index-semantics/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/pagination/page-index-semantics/poetry.lock +++ b/seed/python-sdk/pagination/page-index-semantics/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/path-parameters/poetry.lock b/seed/python-sdk/path-parameters/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/path-parameters/poetry.lock +++ b/seed/python-sdk/path-parameters/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/plain-text/poetry.lock b/seed/python-sdk/plain-text/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/plain-text/poetry.lock +++ b/seed/python-sdk/plain-text/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/property-access/poetry.lock b/seed/python-sdk/property-access/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/property-access/poetry.lock +++ b/seed/python-sdk/property-access/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/public-object/poetry.lock b/seed/python-sdk/public-object/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/public-object/poetry.lock +++ b/seed/python-sdk/public-object/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/python-backslash-escape/poetry.lock b/seed/python-sdk/python-backslash-escape/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/python-backslash-escape/poetry.lock +++ b/seed/python-sdk/python-backslash-escape/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/python-mypy-exclude/no-custom-config/poetry.lock b/seed/python-sdk/python-mypy-exclude/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/python-mypy-exclude/no-custom-config/poetry.lock +++ b/seed/python-sdk/python-mypy-exclude/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/python-mypy-exclude/with-mypy-exclude/poetry.lock b/seed/python-sdk/python-mypy-exclude/with-mypy-exclude/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/python-mypy-exclude/with-mypy-exclude/poetry.lock +++ b/seed/python-sdk/python-mypy-exclude/with-mypy-exclude/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/python-positional-single-property/no-custom-config/poetry.lock b/seed/python-sdk/python-positional-single-property/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/python-positional-single-property/no-custom-config/poetry.lock +++ b/seed/python-sdk/python-positional-single-property/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/python-positional-single-property/with-positional-constructors/poetry.lock b/seed/python-sdk/python-positional-single-property/with-positional-constructors/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/python-positional-single-property/with-positional-constructors/poetry.lock +++ b/seed/python-sdk/python-positional-single-property/with-positional-constructors/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/python-reserved-keyword-subpackages/poetry.lock b/seed/python-sdk/python-reserved-keyword-subpackages/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/python-reserved-keyword-subpackages/poetry.lock +++ b/seed/python-sdk/python-reserved-keyword-subpackages/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/poetry.lock b/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/poetry.lock index 53515816316b..9185b8246815 100644 --- a/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/poetry.lock +++ b/seed/python-sdk/python-streaming-parameter-openapi/with-wire-tests/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/query-param-name-conflict/poetry.lock b/seed/python-sdk/query-param-name-conflict/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/query-param-name-conflict/poetry.lock +++ b/seed/python-sdk/query-param-name-conflict/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/query-parameters-openapi-as-objects/no-custom-config/poetry.lock b/seed/python-sdk/query-parameters-openapi-as-objects/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/query-parameters-openapi-as-objects/no-custom-config/poetry.lock +++ b/seed/python-sdk/query-parameters-openapi-as-objects/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/query-parameters-openapi/no-custom-config/poetry.lock b/seed/python-sdk/query-parameters-openapi/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/query-parameters-openapi/no-custom-config/poetry.lock +++ b/seed/python-sdk/query-parameters-openapi/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/query-parameters/no-custom-config/poetry.lock b/seed/python-sdk/query-parameters/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/query-parameters/no-custom-config/poetry.lock +++ b/seed/python-sdk/query-parameters/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/request-parameters/poetry.lock b/seed/python-sdk/request-parameters/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/request-parameters/poetry.lock +++ b/seed/python-sdk/request-parameters/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/required-nullable/poetry.lock b/seed/python-sdk/required-nullable/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/required-nullable/poetry.lock +++ b/seed/python-sdk/required-nullable/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/reserved-keywords/poetry.lock b/seed/python-sdk/reserved-keywords/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/reserved-keywords/poetry.lock +++ b/seed/python-sdk/reserved-keywords/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/response-property/poetry.lock b/seed/python-sdk/response-property/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/response-property/poetry.lock +++ b/seed/python-sdk/response-property/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/schemaless-request-body-examples/poetry.lock b/seed/python-sdk/schemaless-request-body-examples/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/schemaless-request-body-examples/poetry.lock +++ b/seed/python-sdk/schemaless-request-body-examples/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/server-sent-event-examples/poetry.lock b/seed/python-sdk/server-sent-event-examples/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/server-sent-event-examples/poetry.lock +++ b/seed/python-sdk/server-sent-event-examples/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/server-sent-events-openapi/with-wire-tests/poetry.lock b/seed/python-sdk/server-sent-events-openapi/with-wire-tests/poetry.lock index 53515816316b..9185b8246815 100644 --- a/seed/python-sdk/server-sent-events-openapi/with-wire-tests/poetry.lock +++ b/seed/python-sdk/server-sent-events-openapi/with-wire-tests/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/server-sent-events-resumable/poetry.lock b/seed/python-sdk/server-sent-events-resumable/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/server-sent-events-resumable/poetry.lock +++ b/seed/python-sdk/server-sent-events-resumable/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/server-sent-events/with-wire-tests/poetry.lock b/seed/python-sdk/server-sent-events/with-wire-tests/poetry.lock index 53515816316b..9185b8246815 100644 --- a/seed/python-sdk/server-sent-events/with-wire-tests/poetry.lock +++ b/seed/python-sdk/server-sent-events/with-wire-tests/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/server-url-templating/no-custom-config/poetry.lock b/seed/python-sdk/server-url-templating/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/server-url-templating/no-custom-config/poetry.lock +++ b/seed/python-sdk/server-url-templating/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/simple-api/poetry.lock b/seed/python-sdk/simple-api/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/simple-api/poetry.lock +++ b/seed/python-sdk/simple-api/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/simple-fhir/no-inheritance-for-extended-models/poetry.lock b/seed/python-sdk/simple-fhir/no-inheritance-for-extended-models/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/simple-fhir/no-inheritance-for-extended-models/poetry.lock +++ b/seed/python-sdk/simple-fhir/no-inheritance-for-extended-models/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/single-url-environment-default/poetry.lock b/seed/python-sdk/single-url-environment-default/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/single-url-environment-default/poetry.lock +++ b/seed/python-sdk/single-url-environment-default/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/single-url-environment-no-default/poetry.lock b/seed/python-sdk/single-url-environment-no-default/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/single-url-environment-no-default/poetry.lock +++ b/seed/python-sdk/single-url-environment-no-default/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/streaming-parameter/poetry.lock b/seed/python-sdk/streaming-parameter/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/streaming-parameter/poetry.lock +++ b/seed/python-sdk/streaming-parameter/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/streaming/no-custom-config/poetry.lock b/seed/python-sdk/streaming/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/streaming/no-custom-config/poetry.lock +++ b/seed/python-sdk/streaming/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/streaming/skip-pydantic-validation/poetry.lock b/seed/python-sdk/streaming/skip-pydantic-validation/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/streaming/skip-pydantic-validation/poetry.lock +++ b/seed/python-sdk/streaming/skip-pydantic-validation/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/trace/poetry.lock b/seed/python-sdk/trace/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/trace/poetry.lock +++ b/seed/python-sdk/trace/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/undiscriminated-union-with-response-property/poetry.lock b/seed/python-sdk/undiscriminated-union-with-response-property/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/undiscriminated-union-with-response-property/poetry.lock +++ b/seed/python-sdk/undiscriminated-union-with-response-property/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/undiscriminated-unions/poetry.lock b/seed/python-sdk/undiscriminated-unions/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/undiscriminated-unions/poetry.lock +++ b/seed/python-sdk/undiscriminated-unions/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/union-query-parameters/poetry.lock b/seed/python-sdk/union-query-parameters/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/union-query-parameters/poetry.lock +++ b/seed/python-sdk/union-query-parameters/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/unions-with-local-date/poetry.lock b/seed/python-sdk/unions-with-local-date/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/unions-with-local-date/poetry.lock +++ b/seed/python-sdk/unions-with-local-date/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/unions/flatten-union-request-bodies/poetry.lock b/seed/python-sdk/unions/flatten-union-request-bodies/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/unions/flatten-union-request-bodies/poetry.lock +++ b/seed/python-sdk/unions/flatten-union-request-bodies/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/unions/no-custom-config/poetry.lock b/seed/python-sdk/unions/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/unions/no-custom-config/poetry.lock +++ b/seed/python-sdk/unions/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/unions/union-naming-v1-wire-tests/poetry.lock b/seed/python-sdk/unions/union-naming-v1-wire-tests/poetry.lock index 53515816316b..9185b8246815 100644 --- a/seed/python-sdk/unions/union-naming-v1-wire-tests/poetry.lock +++ b/seed/python-sdk/unions/union-naming-v1-wire-tests/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/unions/union-naming-v1/poetry.lock b/seed/python-sdk/unions/union-naming-v1/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/unions/union-naming-v1/poetry.lock +++ b/seed/python-sdk/unions/union-naming-v1/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/unions/union-utils/poetry.lock b/seed/python-sdk/unions/union-utils/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/unions/union-utils/poetry.lock +++ b/seed/python-sdk/unions/union-utils/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/unknown/poetry.lock b/seed/python-sdk/unknown/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/unknown/poetry.lock +++ b/seed/python-sdk/unknown/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/url-form-encoded/poetry.lock b/seed/python-sdk/url-form-encoded/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/url-form-encoded/poetry.lock +++ b/seed/python-sdk/url-form-encoded/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/validation/no-custom-config/poetry.lock b/seed/python-sdk/validation/no-custom-config/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/validation/no-custom-config/poetry.lock +++ b/seed/python-sdk/validation/no-custom-config/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/validation/with-defaults-parameters/poetry.lock b/seed/python-sdk/validation/with-defaults-parameters/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/validation/with-defaults-parameters/poetry.lock +++ b/seed/python-sdk/validation/with-defaults-parameters/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/validation/with-defaults/poetry.lock b/seed/python-sdk/validation/with-defaults/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/validation/with-defaults/poetry.lock +++ b/seed/python-sdk/validation/with-defaults/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/variables/poetry.lock b/seed/python-sdk/variables/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/variables/poetry.lock +++ b/seed/python-sdk/variables/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/version-no-default/poetry.lock b/seed/python-sdk/version-no-default/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/version-no-default/poetry.lock +++ b/seed/python-sdk/version-no-default/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/version/poetry.lock b/seed/python-sdk/version/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/version/poetry.lock +++ b/seed/python-sdk/version/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/webhook-audience/poetry.lock b/seed/python-sdk/webhook-audience/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/webhook-audience/poetry.lock +++ b/seed/python-sdk/webhook-audience/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/webhooks/poetry.lock b/seed/python-sdk/webhooks/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/webhooks/poetry.lock +++ b/seed/python-sdk/webhooks/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/websocket-bearer-auth/poetry.lock b/seed/python-sdk/websocket-bearer-auth/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/websocket-bearer-auth/poetry.lock +++ b/seed/python-sdk/websocket-bearer-auth/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/websocket-inferred-auth/poetry.lock b/seed/python-sdk/websocket-inferred-auth/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/websocket-inferred-auth/poetry.lock +++ b/seed/python-sdk/websocket-inferred-auth/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/websocket-multi-url/poetry.lock b/seed/python-sdk/websocket-multi-url/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/websocket-multi-url/poetry.lock +++ b/seed/python-sdk/websocket-multi-url/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/websocket/websocket-base/poetry.lock b/seed/python-sdk/websocket/websocket-base/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/websocket/websocket-base/poetry.lock +++ b/seed/python-sdk/websocket/websocket-base/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/websocket/websocket-with_generated_clients-skip_validation/poetry.lock b/seed/python-sdk/websocket/websocket-with_generated_clients-skip_validation/poetry.lock index 2c53dcf0c4a7..3257a05a0e38 100644 --- a/seed/python-sdk/websocket/websocket-with_generated_clients-skip_validation/poetry.lock +++ b/seed/python-sdk/websocket/websocket-with_generated_clients-skip_validation/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/websocket/websocket-with_generated_clients/poetry.lock b/seed/python-sdk/websocket/websocket-with_generated_clients/poetry.lock index 2c53dcf0c4a7..3257a05a0e38 100644 --- a/seed/python-sdk/websocket/websocket-with_generated_clients/poetry.lock +++ b/seed/python-sdk/websocket/websocket-with_generated_clients/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] diff --git a/seed/python-sdk/x-fern-default/poetry.lock b/seed/python-sdk/x-fern-default/poetry.lock index 0bc60b692141..7b71273a60c9 100644 --- a/seed/python-sdk/x-fern-default/poetry.lock +++ b/seed/python-sdk/x-fern-default/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" description = "Happy Eyeballs for asyncio" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, + {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, ] [[package]] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, - {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]]