Skip to content

Implement OpenAPI v3 generator - #548

Draft
Smaug123 wants to merge 11 commits into
mainfrom
openapi-3
Draft

Implement OpenAPI v3 generator#548
Smaug123 wants to merge 11 commits into
mainfrom
openapi-3

Conversation

@Smaug123

Copy link
Copy Markdown
Owner

First commit is pure slop (GPT-5.6 Sol Ultra)

@Smaug123
Smaug123 force-pushed the openapi-3 branch 2 times, most recently from 688da70 to e3cc53c Compare July 11, 2026 13:56
Smaug123 and others added 5 commits July 11, 2026 20:14
The HTTP client generator had no case for an option-typed [<Query>]
parameter, so it fell through to the scalar path and emitted
`param.ToString()`. For `Some 3` that yields `page=Some%283%29`, and for
`None` it throws a NullReferenceException, since F#'s None is null at
runtime.

An option-typed query parameter now contributes zero or one key=value
pair, via Option.map/Option.toList, so None is omitted from the URL and
an all-None query leaves the URL bare. Query strings made only of
required parameters are byte-identical to before, so no existing
generated client changes.

This is the client-side half of #546, without that PR's change to
SwaggerClientGenerator (which additionally made unannotated Swagger 2
query parameters optional, and was rejected for the churn). Nothing
currently generates optional query parameters; this makes it possible
for them to be generated correctly when they are.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: GPT-5.6 Sol Ultra
The petstore spec's `limit` on GET /pets is `required: false`, so the
generator plans it as `int option` — but nothing exercised ListPets at
runtime, so the broken encoding it previously produced
(`limit=Some%2825%29`, and a NullReferenceException on None) went
unnoticed. Regenerating on top of the option-aware query encoding fixes
the emitted client; this pins the behaviour with a test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Smaug123
Smaug123 changed the base branch from main to optional-query-encoding July 11, 2026 19:21
Base automatically changed from optional-query-encoding to main July 12, 2026 09:21
Smaug123 and others added 6 commits July 12, 2026 10:26
# Conflicts:
#	CHANGELOG.md
#	ConsumePlugin/GeneratedRestClient.fs
#	ConsumePlugin/RestApiExample.fs
#	WoofWare.Myriad.Plugins.Test/TestHttpClient/TestOptionalQueryParam.fs
#	WoofWare.Myriad.Plugins/HttpClientGenerator.fs
The OpenAPI 3.0 planner ignored `security` and `components.securitySchemes`
entirely, so a document which required authentication silently generated a
client which sent none.

Each security scheme an operation requires now becomes an abstract property on
the generated interface, and hence a `unit -> string` argument of `make` which
is called afresh on every request needing that credential. Each operation sends
exactly the credentials its own requirement asks for: `security: []` sends none
and doesn't even ask for them, and a requirement naming several schemes sends
all of them. Where an operation offers alternatives, the first satisfiable one
is applied, and which one that is is visible in the generated source; the new
`SecuritySchemes` Myriad parameter restricts the choice. An operation with no
satisfiable requirement is now a build failure rather than a silently
unauthenticated client.

Supported schemes are the header-carried ones: `apiKey` in a header, `http` of
any scheme, and `oauth2`/`openIdConnect`, for which the caller supplies the
`Authorization` header value. No token flow is performed.

Expressing this needed a way to set a header on only some of an interface's
members, so this also adds `[<HeaderFromProperty(header, propertyName)>]` to the
`HttpClient` generator: the named property still becomes a `unit -> _` argument
of `make`, but only the members which name it call it. `[<RestEase.Header>]` on
a property continues to apply to every member. The property may be named by a
literal or by `nameof Unchecked.defaultof<IMyApi>.TheProperty`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two bugs found by a Codex review of 5703dd1, both of which would have produced
a broken client from a valid document.

An interface property whose name lowercases to `client` collided with the
HttpClient argument the generator adds to `make`, so a security scheme named
"client" produced `let make (client : unit -> string) (client : HttpClient)`,
which doesn't compile. The HttpClient generator now names its own argument
around the properties, as it already did for the `queryString` binding.

Two schemes in one requirement could demand the same header — an `http` scheme
alongside an `oauth2` one, say, both of which carry `Authorization`. Each is
individually representable, so we selected the requirement and then emitted two
`Headers.Add ("Authorization", _)` calls, which throws on every request.
Such an alternative is now unsatisfiable, so a later alternative can be chosen
instead, or the build fails.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Taking the first satisfiable alternative meant that a document offering
several — as real specs commonly do — silently chose an authentication method
on the caller's behalf. Gitea's Swagger spec is the motivating example: it
offers seven alternatives on every endpoint, four of which we can carry, so
"first wins" would conscript every consumer into HTTP Basic.

A document lists its alternatives in no particular order, so document order is
not a preference. Exactly one alternative must now be satisfiable; several is
an AmbiguousSecurity diagnostic naming the candidates, and the caller says
which they want with the SecuritySchemes parameter. This matches the Swagger
2.0 generator.

The empty requirement {} counts as a candidate like any other: an operation
offering both "no credentials" and a real scheme is a genuine choice, and
sending nothing is exactly the silently-unauthenticated client this all exists
to prevent. Setting SecuritySchemes empty takes that alternative wherever the
document offers it.

Alternatives naming the same schemes are deduplicated first, since a repeated
alternative is one choice rather than two.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant