Usages:
  conventions: .goga/usages/conventions.md
  swax-openapi: .goga/usages/cooks/swax-openapi.md
  click: .goga/usages/cooks/click.md

Annotations: |
  Use `conventions` for code writing rules and testing.
  Use `swax-openapi` for parsing specs via swax.openapi and the endpoint-extraction patterns over the parsed dict (both Swagger 2.0 and OpenAPI 3.x).
  Use `click` for mapping SpecParseError to ClickException in `load_spec` (uniform non-zero exit).

  Use relative imports inside the cell.
  Extraction is pure logic over the parsed dict — $ref are already inlined by Prance (see `swax-openapi`).
  Extraction routes each spec by version detected from its content (swagger "2.0" vs openapi "3.x") and normalizes both formats to the same JSON-Schema shape on `Endpoint`.
  Keep only HTTP methods in path-items; merge path-item parameters into operations when present.

---

"build_endpoint_id(method: str, path: str) -> endpoint_id: str":
  location: endpoint_id.py
  annotations: |
    Deterministic id derived from an HTTP method and path template.

    `method`: HTTP method (e.g. POST, GET) — normalized to lower.
    `path`: path template (e.g. /v1/API/{name}) — leading slash and braces stripped.
    `endpoint_id`: stable lowercased identifier, e.g. v1_api_name_post.

    Algorithm:
    1. Drop a single leading "/" from `path`.
    2. Remove "{" and "}" from `path` (keep the parameter name).
    3. Lowercase the result.
    4. Replace every "/" with "_".
    5. Replace every "-" with "_" (paths may carry hyphens; the id is consumed as a pytest fixture name and as a package directory by generate, so it must stay a valid Python identifier).
    6. Append "_" + method.lower().

    Requirements:
    - Verified on: POST /v1/API/{name} -> v1_api_name_post; GET /clients/startup -> clients_startup_get; DELETE /clients/profile -> clients_profile_delete; GET /clients/payment-details -> clients_payment_details_get.

    Constraints:
    - Pure function — no I/O, no parsing.

"Endpoint(method: str, path: str, request: dict[str, Any], response: dict[str, Any], query_params: dict[str, Any], description: str)":
  location: endpoint.py
  annotations: |
    A single endpoint extracted from a spec operation: method, path, and resolved request/response shapes.

    `method`: HTTP method, lowercased.
    `path`: path template with parameters in braces, e.g. /clients/{id}.
    `request`: resolved request-body schema (primary JSON content) or {}.
    `response`: {status_code: resolved_schema} for each response (primary JSON content).
    `query_params`: {param_name: schema} for query parameters only.
    `description`: operation description or "".

    Use `conventions` for pydantic model rules.

    Constraints:
    - `request`/`response`/`query_params` hold already-resolved schemas (Prance inlined refs — see `swax-openapi`), nullable-normalized to JSON-Schema union types by `extract_endpoints`.
  properties:
    "id -> str": |
      Stable identifier derived via `build_endpoint_id` from the endpoint's method and path. Computed field — not a constructor input.

"load_spec(spec_path: pathlib.Path) -> spec: dict[str, Any]":
  location: loader.py
  annotations: |
    Parse a spec file into a fully dereferenced dict via swax.openapi.

    `spec_path`: path to a .yaml/.yml/.json spec file (resolved from project root).
    `spec`: dereferenced specification dict with $ref inlined.

    Algorithm:
    1. Call swax.openapi.parse_spec(`spec_path`).
    2. On SpecParseError, raise click.ClickException carrying the path and reason.

    Requirements:
    - $ref must already be resolved by Prance — never resolve references manually downstream.

    Use `swax-openapi` for the parse_spec contract and error mapping.

"detect_spec_version(spec: dict[str, Any]) -> version: str":
  location: extract.py
  annotations: |
    Determine the spec format by inspecting the parsed spec's content, independent of the declarative config type.

    `spec`: dereferenced specification dict (output of `load_spec`).
    `version`: format identifier — "swagger" for Swagger 2.0, "openapi" for OpenAPI 3.x.

    Algorithm:
    1. If `spec` has a top-level swagger key (Swagger 2.0), return "swagger".
    2. Otherwise, if `spec` has a top-level openapi key (OpenAPI 3.x), return "openapi".
    3. Otherwise raise ValueError — the spec declares neither version, which contradicts both specifications.

    Requirements:
    - Detection is by spec content only — never by the declarative config type field.
    - A spec with neither a swagger nor an openapi top-level key is invalid; raise ValueError (a valid Swagger 2.0 spec must carry swagger, a valid OpenAPI 3.x spec must carry openapi).

    Constraints:
    - Pure function — no I/O, no parsing; raises ValueError on an unrecognized format.

"extract_endpoints(spec: dict[str, Any]) -> endpoints: list[Endpoint]":
  location: extract.py
  annotations: |
    Walk a parsed spec's operations and build an `Endpoint` for each method+path, routing field extraction by the detected format.

    `spec`: dereferenced specification dict (output of `load_spec`).
    `endpoints`: one `Endpoint` per declared operation.

    Algorithm:
    1. Read spec["paths"].
    2. Detect the spec format via `detect_spec_version`.
    3. For each path-item, for each HTTP method present in (get, post, put, delete, patch, options, head), take the operation.
    4. Build an `Endpoint` from the operation using the format-specific structure chosen by the detected version (see Requirements), normalizing every extracted schema to the JSON-Schema union nullable form (see Requirements).
    5. Compute each `Endpoint` id via `build_endpoint_id`.

    Requirements:
    - Skip non-method keys in path-items (parameters, summary).
    - Path-item parameters are inherited by all operations — merge with operation parameters when extracting query params.
    - Primary content type is application/json; absent fields default to {}.
    - OpenAPI 3.x: request from the operation requestBody content application/json schema; response from each responses[code] content application/json schema; query from each parameters[] entry whose in is query, via its nested schema.
    - Swagger 2.0: request from the parameter whose in is body, via its root schema; response from responses[code] schema directly (no content wrapper); query from each parameters[] entry whose in is query, via its inlined type/format/items/enum/default/description/x-nullable fields — the canonical _TYPE_FIELDS set from `swax-openapi`, which includes x-nullable so the keyword reaches nullable-normalization rather than being dropped by field filtering.
    - Each extracted schema is nullable-normalized to the JSON-Schema union form: OpenAPI nullable: true and Swagger x-nullable: true both become a type list including "null" (with an anyOf fallback when a single type cannot host the union); the originating key is dropped; recursion runs through properties, items, additionalProperties, and anyOf/oneOf/allOf. Required because the runtime jsonschema validator ignores both keywords, so an un-normalized fragment rejects null.

    Constraints:
    - Pure logic over `spec` — no I/O, no $ref resolution.
    - Both formats must yield the same normalized schema shape for equivalent operations.
    - A spec declaring neither a swagger nor an openapi version is invalid — `detect_spec_version` raises ValueError, which `extract_endpoints` propagates without swallowing.
    - Do not merge basePath/servers into path; path stays the path-item key.
    - Do not account for consumes/produces; media-type priority stays with application/json.
    - Do not extract formData or file-upload parameters (outside the query_params model).

    Use `swax-openapi` for the operation/field extraction patterns for both formats.

---

Author: Goga
CreatedAt: 02/07/26
Description: |
  Endpoint model, deterministic endpoint id, spec parsing via swax.openapi, and endpoint extraction for Swagger 2.0 and OpenAPI 3.x.
