# Domain Concepts

Key domain concepts, terminology, and mental models for this project.

## Glossary

| Term | Definition |
|---|---|
| **ClientSpec** | The complete metadata describing a generated client: name, endpoints, and configuration prefix. The intermediate representation between introspection and code generation. Language-agnostic — the same `ClientSpec` drives Python, Go, and Flutter generators. |
| **EndpointInfo** | One callable operation: an HTTP method on a specific URL path, with its parameters and description. |
| **ParameterInfo** | A single input to an endpoint: its name, location (path/query/body), type, and whether it's required. |
| **Introspection** | The process of booting a Pyramid app and reading its route/view registry to discover endpoints. Delegated to `pyramid-introspector`, which handles route discovery and Cornice/pycornmarsh enrichment via its extension system. |
| **Introspection adapter** | The local `introspection/core.py` that calls `pyramid-introspector`, flattens its `RouteInfo`/`ViewInfo` output into `EndpointInfo`, and applies client-builder-specific filtering. |
| **Code generation** | Rendering Jinja2 templates with a `ClientSpec` to produce source files (Python packages, Go modules, or Dart packages). |
| **Variant** | A specific language + transport combination generated by `pclient-build`. Current variants: `python_requests`, `python_httpx`, `go`, `flutter`. All variants are generated in a single invocation into subdirectories. The two Python variants get distinct PyPI project names via a variant suffix (e.g., `payments-client-requests` and `payments-client-httpx`) so they can be published as separate packages. |
| **Pyramid extension (`includeme`)** | A function that Pyramid calls during configuration to register the client on the request object (e.g., `request.payments_client`). Python-only. |
| **Verb detection** | Using NLTK WordNet to identify action verbs at the end of URL paths, enabling method names like `cancel_charge` instead of `create_charge_cancel`. |
| **Detail endpoint** | A path that ends with a path parameter (e.g., `/charges/{id}`), representing a single resource. Methods are singularized: `get_charge` (Python), `GetCharge` (Go), or `getCharge` (Dart). |
| **Collection endpoint** | A path that ends with a resource name (e.g., `/charges`), representing a list. GET uses `list_` prefix: `list_charges` (Python), `ListCharges` (Go), or `listCharges` (Dart). |
| **SchemaInfo** | A captured Marshmallow schema (name + fields) ready for code generation. Attached to endpoints for request body, querystring, or response serialization. In Go, rendered as a struct with JSON tags. In Dart, rendered as a class with `fromJson`/`toJson`. |
| **SchemaFieldInfo** | A single field within a `SchemaInfo`: its name, Marshmallow field type (e.g., `"Integer"`), required flag, and metadata. |
| **Schema-based serialization** | In Python: the generated client uses copies of the server's Marshmallow schemas (`dump()` for requests, `load()` for responses). In Go: schemas become structs that are marshaled/unmarshaled via `encoding/json`. In Dart: schemas become classes with `fromJson` factory constructors and `toJson` methods. |
| **Go struct params** | In the Go client, when a schema exists, the method accepts a struct pointer as the parameter (e.g., `req *ChargesRequestSchema`) rather than individual arguments. This is idiomatic Go since Go lacks default parameter values. |
| **Go functional options** | The `NewClient(baseURL, opts ...Option)` pattern used in the Go client. Provides `WithAuthToken()` and `WithHTTPClient()` option functions. |
| **Go type mapping** | Marshmallow field types map to Go types: String→string, Integer→int, Float→float64, Boolean→bool, DateTime→time.Time, List→[]interface{}, Dict→map[string]interface{}. Optional fields use pointer types (*string, *int) with `json:",omitempty"` tags. |
| **Dart type mapping** | Marshmallow field types map to Dart types: String→String, Integer→int, Float→double, Boolean→bool, DateTime→DateTime, List→List\<dynamic\>, Dict→Map\<String, dynamic\>. Optional fields use nullable types (String?, int?). DateTime fields use `DateTime.parse()` for deserialization and `toIso8601String()` for serialization. |
| **Dart model classes** | In the Flutter client, schemas are generated as Dart classes with final fields, a constructor with named parameters (`required` for mandatory fields), a `fromJson` factory constructor, and a `toJson` method. No external serialization dependency required. |
| **Barrel export** | A Dart convention: a single `.dart` file in `lib/` that re-exports all public API from `lib/src/`. Allows consumers to `import 'package:name/name.dart'` to get everything. The file name uses Jinja expressions in the template (`{{package_name}}.dart.j2`). |
| **pycornmarsh metadata** | An alternative schema declaration pattern using custom Pyramid view predicates (`pcm_request`, `pcm_responses`). Provides explicit location-to-schema mapping and per-status-code response schemas. Takes precedence over Cornice's `schema` kwarg when present. |
| **pcm_request** | A dict passed to Cornice decorators mapping locations (`"body"`, `"querystring"`) to Marshmallow schema classes. Provides more explicit schema location info than the `schema` kwarg + validator inference pattern. |
| **pcm_responses** | A dict passed to Cornice decorators mapping HTTP status codes (e.g., `200`, `400`) to Marshmallow schema classes. Enables per-status-code response typing. The first 2xx schema becomes the endpoint's primary `response_schema`. |
| **Schema renaming** | The generator renames schemas whose names lack a role suffix (e.g., `ChargeSchema`) based on the endpoint path + usage role. `ChargeSchema` on `POST /api/v1/charges` becomes `ChargesRequestSchema`. Schemas that already have role suffixes (`RequestSchema`, `ResponseSchema`, `QuerySchema`, `BodySchema`, `PathSchema`, `ErrorSchema`) are left unchanged. Applied to Python, Go, and Flutter generators. |
| **Role suffix** | A schema name suffix that communicates how the schema is used: `RequestSchema` (body), `ResponseSchema` (response), `QuerySchema` (querystring), `BodySchema` (body in composite), `PathSchema` (path params), `ErrorSchema` (error responses). |
| **Versioned output** | When endpoints have API version prefixes (`/api/v1/...`), the generator creates per-version subdirectories (`v1/`, `v2/`). In Python: each version has `client.py` and `schemas.py`. In Go: each version is a sub-package with `client.go` and `types.go`. In Dart: each version has `client.dart` and `models.dart` under `lib/src/`. |
| **Version sub-client** | A per-version client class/struct. In Python: `V1Client` receives a shared session from the root client. In Go: `v1.Client` receives shared `*http.Client` and auth token. In Dart: `V1Client` receives shared `http.Client` and auth token. |
| **Common generator logic** | Language-agnostic functions in `generator/common.py`: `group_by_version()`, `rename_schemas()`, `collect_schemas()`, `iter_schemas()`. Shared by Python, Go, and Flutter generators. |

## Mental Models

### The protoc analogy

Think of `pclient-build` like `protoc` for gRPC: you define your API in one place (Pyramid routes + Cornice services), run a build tool, and get typed client packages on disk — one per language/transport variant. The generated code is committed to the consuming project's repo and treated as source.

### Three-layer pipeline

```
Introspection → ClientSpec → Code Generation (per variant)
```

Each layer is independent and testable:
- Introspection only reads Pyramid's registry — it doesn't generate any code.
- ClientSpec is a plain data structure — it can be built manually for testing.
- Code generation only reads a ClientSpec and renders templates — it doesn't touch Pyramid.
- Multiple generators consume the same ClientSpec independently.

### Method naming as path interpretation

URL paths encode intent: `/charges` is a collection, `/charges/{id}` is a detail, and `/charges/{id}/cancel` is an action. The naming module interprets this structure to produce natural method names without requiring explicit annotations in the source app. Python uses snake_case (`list_charges`), Go uses PascalCase (`ListCharges`), and Dart uses camelCase (`listCharges`) — all derived from the same underlying logic.

### Schema naming as role communication

Server-side schemas are named by domain concept (`ChargeSchema`). The generated client renames them by usage role (`ChargesRequestSchema`), because the client consumer cares about *what they send* vs *what they receive*, not the server's internal model names.

## Invariants

- Every generated method maps to exactly one HTTP method on one URL path.
- Path parameters in the URL become required positional arguments in the method signature.
- In Python: querystring parameters become optional keyword arguments; body parameters become individual keyword arguments.
- In Go: when a schema exists, body/query parameters are passed as a struct pointer; without a schema, they are individual function arguments.
- In Dart: path parameters are required positional arguments; body parameters are named parameters with `required` keyword; querystring parameters are nullable named parameters. When a schema exists, the method accepts a model instance.
- In Python: schemas use Marshmallow `dump()` for serialization and `load()` for deserialization. In Go: schemas are `encoding/json` structs. In Dart: schemas are classes with `fromJson`/`toJson`.
- Response deserialization uses schema when declared; otherwise returns raw JSON (Python `dict` / Go `map[string]interface{}` / Dart `Map<String, dynamic>`).
- The generated Python client is a standalone package (requests or httpx + optional marshmallow). The generated Go client is a Go module using only the standard library. The generated Dart client is a package depending only on the `http` package.
- Re-running `pclient-build` with the same inputs produces identical output (deterministic generation).
- A single `pclient-build` invocation generates all four variants (python_requests, python_httpx, go, flutter).
