Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
9276ef8
fix(core): warn instead of silently dropping unrecognized path-item o…
khayashi4337 Sep 20, 2026
c83850f
feat(core): generate OpenAPI 3.2 query/additionalOperations and in:qu…
khayashi4337 Sep 22, 2026
a52ebe2
fix(core): gate OpenAPI 3.2 operations behind generator capability
khayashi4337 Sep 22, 2026
87fa468
feat(go): generate OpenAPI 3.2 query/additionalOperations and in:quer…
khayashi4337 Sep 22, 2026
78308cc
Add OpenAPI 3.2 support to Python client, harden Go method emission
khayashi4337 Sep 22, 2026
e2b405d
feat(typescript-fetch): generate OpenAPI 3.2 query/additionalOperatio…
khayashi4337 Sep 22, 2026
c901798
feat(rust): generate OpenAPI 3.2 query/additionalOperations and in:qu…
khayashi4337 Sep 22, 2026
d0e1935
feat(ruby): generate OpenAPI 3.2 query/additionalOperations and in:qu…
khayashi4337 Sep 22, 2026
043b37a
test: delete generated temp dirs deterministically, fix probe hang
khayashi4337 Sep 22, 2026
ef3f821
feat(php): generate OpenAPI 3.2 query/additionalOperations and in:que…
khayashi4337 Sep 22, 2026
f30e9f3
feat(csharp): generate OpenAPI 3.2 query/additionalOperations and in:…
khayashi4337 Sep 22, 2026
5ca6a5e
feat(kotlin): generate OpenAPI 3.2 query/additionalOperations and in:…
khayashi4337 Sep 22, 2026
51fa9b9
fix(ruby): keep bodies on 3.2 verbs, preserve '//' in querystring, ga…
khayashi4337 Sep 22, 2026
62a98c2
fix(php): require psr/http-message ^2.10, avoid \$uri collision in 3.…
khayashi4337 Sep 22, 2026
e49e09e
fix(csharp): scan webhooks for operation-input models, skip normalize…
khayashi4337 Sep 22, 2026
6b82674
fix(kotlin): rename params colliding with template locals, body set f…
khayashi4337 Sep 22, 2026
9f2676e
fix(php): rename spec params that collide with generated function locals
khayashi4337 Sep 23, 2026
910b590
fix(kotlin): clone shared deepObject vars, guard normalized name coll…
khayashi4337 Sep 23, 2026
2f0ffbf
test(rust): delete temp dirs reliably in all codegen tests
khayashi4337 Sep 23, 2026
bc20a2c
fix(codegen): match allOf-inherited form required flags by baseName
khayashi4337 Sep 23, 2026
a00d51c
test(php): pin unique signature under prependFormOrBodyParameters
khayashi4337 Sep 23, 2026
94aca3a
fix(kotlin-client): prevent template-local/member collisions with spe…
khayashi4337 Sep 23, 2026
1cf67f2
chore(samples): regenerate ruby/php samples for earlier template fixes
khayashi4337 Sep 24, 2026
7ab9da5
fix(codegen): limit form required flags to the allOf chain
khayashi4337 Sep 24, 2026
19694ac
fix(kotlin-client): qualify inherited member refs and revert spring d…
khayashi4337 Sep 24, 2026
9074531
fix(kotlin-client): escape $ in wire names for all libraries
khayashi4337 Sep 24, 2026
f1575f3
fix(kotlin-client): qualify multiplatform wrap-map receiver as this.v…
khayashi4337 Sep 24, 2026
07530ab
fix(kotlin-client): name the form-body ParametersBuilder lambda param
khayashi4337 Sep 24, 2026
75be8f4
fix(kotlin-client): actually send the vertx form body
khayashi4337 Sep 24, 2026
3a953e5
fix(kotlin-client): append whole FormPart values in multipart bodies
khayashi4337 Sep 24, 2026
a274eae
chore(samples): regenerate kotlin samples for the existing-bug fixes
khayashi4337 Sep 24, 2026
2ea2e94
fix(core): skip in:querystring operations for generators without support
khayashi4337 Sep 30, 2026
caf09b5
fix(go): restore query-parameter ordering when combining raw and enco…
khayashi4337 Sep 30, 2026
da17511
fix(python): preserve verbatim casing for OpenAPI 3.2 method names
khayashi4337 Sep 30, 2026
b769bf9
fix(typescript-fetch): warn on fetch-forbidden methods regardless of …
khayashi4337 Sep 30, 2026
19cf404
test(core): pin root-security propagation to OpenAPI 3.2 additionalOp…
khayashi4337 Sep 30, 2026
e5ec5f4
fix(core): tighten and widen dropped-operation detection regex
khayashi4337 Sep 30, 2026
5bf2f8d
fix(rust): drain cargo's output pipe concurrently to avoid a deadlock
khayashi4337 Sep 30, 2026
7df495f
fix(core): honor supportsAdditionalOperations() when discovering call…
khayashi4337 Sep 30, 2026
06cef26
fix(typescript-fetch): don't treat a '?' inside the URL fragment as a…
khayashi4337 Sep 30, 2026
4ec1d58
test(core): update webhook-operations.yaml references after rename
khayashi4337 Sep 30, 2026
7f1bcb8
fix(kotlin-client): fix 4 pre-existing defects found while reviewing …
khayashi4337 Sep 30, 2026
571b164
test(core): pin allOf-chain required-collection edge cases
khayashi4337 Sep 30, 2026
0841d62
fix(core): stop additionalOperations from overwriting fixed methods i…
khayashi4337 Sep 30, 2026
06b0735
Merge remote-tracking branch 'upstream/master' into openapi-3.2-codegen
khayashi4337 Oct 1, 2026
e9d413c
fix(k6): skip OpenAPI 3.2 query operations instead of generating http…
khayashi4337 Oct 1, 2026
0b60507
Merge remote-tracking branch 'upstream/master' into openapi-3.2-codegen
khayashi4337 Oct 20, 2026
f330b87
chore(samples): regenerate oneOf-not-enum samples after upstream merge
khayashi4337 Oct 20, 2026
fc656cf
fix(typescript-fetch): drop duplicated parameter copies, flag 3.2 web…
khayashi4337 Oct 20, 2026
1a4f760
Merge remote-tracking branch 'upstream/master' into openapi-3.2-codegen
khayashi4337 Oct 21, 2026
ad19014
Address remaining cubic-dev-ai test findings for OpenAPI 3.2 codegen
khayashi4337 Oct 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -387,4 +387,18 @@ default List<Operation> divideOperationsByContentType(OpenAPI openAPI, String pa

Set<String> getOpenapiGeneratorIgnoreList();

/**
* Whether this generator can emit operations defined through OpenAPI 3.2's
* {@code query} fixed field and {@code additionalOperations} map, i.e. HTTP
* methods outside the original eight fixed methods. Generators whose
* templates embed enumerated method constants (e.g. {@code HttpMethod.QUERY})
* must keep the default {@code false}: the affected operations are then
* skipped with a warning instead of producing uncompilable code.
*
* @return true if the generator supports OpenAPI 3.2 query/additional operations
*/
default boolean supportsAdditionalOperations() {
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public class CodegenParameter implements IJsonSchemaValidationProperties {
* If a query parameter should be serialized as json
*/
public boolean queryIsJsonMimeType;
/**
* OpenAPI 3.2 {@code in: querystring} parameter: the parameter value describes
* the entire query string (via {@code content}), not a single name=value pair.
*/
public boolean isQueryStringParam;
/**
* datatype is the generic inner parameter of a std::optional for C++, or Optional (Java)
*/
Expand Down Expand Up @@ -270,6 +275,7 @@ public CodegenParameter copy() {
output.isArray = this.isArray;
output.isMap = this.isMap;
output.queryIsJsonMimeType = this.queryIsJsonMimeType;
output.isQueryStringParam = this.isQueryStringParam;
output.isOptional = this.isOptional;
output.isExplode = this.isExplode;
output.style = this.style;
Expand All @@ -294,7 +300,7 @@ public int hashCode() {
isFormStyle, isSpaceDelimited, isPipeDelimited,
jsonSchema, isString, isNumeric, isInteger, isLong, isNumber, isFloat, isDouble, isDecimal,
isByteArray, isBinary, isBoolean, isDate, isDateTime, isUuid, isUri, isEmail, isPassword,
isFreeFormObject, isAnyType, isArray, isMap, queryIsJsonMimeType, isOptional, isFile, isEnum, isEnumRef, _enum, allowableValues,
isFreeFormObject, isAnyType, isArray, isMap, queryIsJsonMimeType, isQueryStringParam, isOptional, isFile, isEnum, isEnumRef, _enum, allowableValues,
items, mostInnerItems, additionalProperties, vars, requiredVars, vendorExtensions, hasValidation,
getMaxProperties(), getMinProperties(), isNullable, isDeprecated, required, getMaximum(),
getExclusiveMaximum(), getMinimum(), getExclusiveMinimum(), getMaxLength(), getMinLength(),
Expand All @@ -312,6 +318,7 @@ public boolean equals(Object o) {
CodegenParameter that = (CodegenParameter) o;
return isFormParam == that.isFormParam &&
isQueryParam == that.isQueryParam &&
isQueryStringParam == that.isQueryStringParam &&
isPathParam == that.isPathParam &&
isHeaderParam == that.isHeaderParam &&
isCookieParam == that.isCookieParam &&
Expand Down Expand Up @@ -429,6 +436,7 @@ public String toString() {
final StringBuilder sb = new StringBuilder("CodegenParameter{");
sb.append("isFormParam=").append(isFormParam);
sb.append(", isQueryParam=").append(isQueryParam);
sb.append(", isQueryStringParam=").append(isQueryStringParam);
sb.append(", isPathParam=").append(isPathParam);
sb.append(", isHeaderParam=").append(isHeaderParam);
sb.append(", isCookieParam=").append(isCookieParam);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1397,8 +1397,20 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
// we need to add all request and response bodies to processed schemas
if (pathItems != null) {
for (Map.Entry<String, PathItem> e : pathItems.entrySet()) {
for (Map.Entry<PathItem.HttpMethod, Operation> op : e.getValue().readOperationsMap().entrySet()) {
String opId = getOrGenerateOperationId(op.getValue(), e.getKey(), op.getKey().toString());
Map<String, Operation> pathOperations = new LinkedHashMap<>();
e.getValue().readOperationsMap().forEach((method, operation) -> {
// HttpMethod.QUERY is in the enum map too - skip it for
// generators that cannot emit the 3.2 operations
if (method == PathItem.HttpMethod.QUERY && !supportsAdditionalOperations()) {
return;
}
pathOperations.put(method.toString(), operation);
});
if (supportsAdditionalOperations() && e.getValue().getAdditionalOperations() != null) {
pathOperations.putAll(e.getValue().getAdditionalOperations());
}
for (Map.Entry<String, Operation> op : pathOperations.entrySet()) {
String opId = getOrGenerateOperationId(op.getValue(), e.getKey(), op.getKey());
// process request body
RequestBody b = ModelUtils.getReferencedRequestBody(openAPI, op.getValue().getRequestBody());
Schema requestSchema = null;
Expand Down Expand Up @@ -5154,7 +5166,11 @@ public CodegenOperation fromOperation(String path,
param = ModelUtils.getReferencedParameter(this.openAPI, param);

CodegenParameter p = fromParameter(param, imports);
p.setContent(getContent(param.getContent(), imports, "RequestParameter" + toModelName(param.getName())));
// for `in: querystring` the content schema only describes the wire format;
// the codegen parameter is a plain string and needs no content metadata/imports
if (!(param instanceof QueryStringParameter) && !"querystring".equalsIgnoreCase(param.getIn())) {
p.setContent(getContent(param.getContent(), imports, "RequestParameter" + toModelName(param.getName())));
}

// ensure unique params
if (ensureUniqueParams) {
Expand All @@ -5173,6 +5189,10 @@ public CodegenOperation fromOperation(String path,
headerParams.add(p.copy());
} else if (param instanceof CookieParameter || "cookie".equalsIgnoreCase(param.getIn())) {
cookieParams.add(p.copy());
} else if (param instanceof QueryStringParameter || "querystring".equalsIgnoreCase(param.getIn())) {
// keep it in queryParams so it lands in the operation signature;
// templates use isQueryStringParam to emit the whole-query-string form
queryParams.add(p.copy());
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
} else {
LOGGER.warn("Unknown parameter type {} for {}", p.baseType, p.baseName);
}
Expand Down Expand Up @@ -5501,14 +5521,31 @@ public CodegenCallback fromCallback(String name, Callback callback, List<Server>
u.vendorExtensions.putAll(pi.getExtensions());
}

Stream.of(
Pair.of("get", pi.getGet()),
Pair.of("head", pi.getHead()),
Pair.of("put", pi.getPut()),
Pair.of("post", pi.getPost()),
Pair.of("delete", pi.getDelete()),
Pair.of("patch", pi.getPatch()),
Pair.of("options", pi.getOptions()))
Stream.Builder<Pair<String, Operation>> callbackOps = Stream.builder();
callbackOps.add(Pair.of("get", pi.getGet()));
callbackOps.add(Pair.of("head", pi.getHead()));
callbackOps.add(Pair.of("put", pi.getPut()));
callbackOps.add(Pair.of("post", pi.getPost()));
callbackOps.add(Pair.of("delete", pi.getDelete()));
callbackOps.add(Pair.of("patch", pi.getPatch()));
callbackOps.add(Pair.of("options", pi.getOptions()));
callbackOps.add(Pair.of("trace", pi.getTrace()));
// additionalOperations keys are HTTP method names sent verbatim
Set<String> verbatimMethods = new HashSet<>();
if (supportsAdditionalOperations()) {
callbackOps.add(Pair.of("query", pi.getQuery()));
if (pi.getAdditionalOperations() != null) {
pi.getAdditionalOperations().forEach((m, o) -> {
callbackOps.add(Pair.of(m, o));
verbatimMethods.add(m);
});
}
} else if (pi.getQuery() != null
|| (pi.getAdditionalOperations() != null && !pi.getAdditionalOperations().isEmpty())) {
LOGGER.warn("Callback '{}' on expression '{}' declares OpenAPI 3.2 query/additionalOperations but generator '{}' does not support them; those operations will be missing from the generated output",
name, expression, getName());
}
callbackOps.build()
.filter(p -> p.getValue() != null)
.forEach(p -> {
String method = p.getKey();
Expand All @@ -5532,6 +5569,9 @@ public CodegenCallback fromCallback(String name, Callback callback, List<Server>
op.getExtensions().put("x-callback-request", true);

CodegenOperation co = fromOperation(expression, method, op, servers);
if (verbatimMethods.contains(method)) {
co.httpMethod = method;
}
if (genId) {
co.operationIdOriginal = null;
// legacy (see `fromOperation()`)
Expand Down Expand Up @@ -5646,7 +5686,22 @@ public CodegenParameter fromParameter(Parameter parameter, Set<String> imports)
// e.g. #/components/schemas/list_pageQuery_parameter => toModelName(list_pageQuery_parameter)
String parameterModelName = null;

if (parameter.getSchema() != null) {
// OpenAPI 3.2 `in: querystring`: the parameter describes the entire query
// string via `content`. Codegen exposes it as a plain string - the caller
// supplies the already-encoded query string (typed serialization of the
// content schema is not generated).
boolean isQueryStringParam = parameter instanceof QueryStringParameter
|| "querystring".equalsIgnoreCase(parameter.getIn());
if (isQueryStringParam) {
parameterSchema = new StringSchema();
parameterModelName = getParameterDataType(parameter, parameterSchema);
if (!supportsQueryStringParameters()) {
once(LOGGER).warn("Encountered an `in: querystring` parameter ({}): this generator has no "
+ "dedicated querystring serialization support - generated code may drop the "
+ "parameter or emit a regular name=value pair instead of the whole query string.",
parameter.getName());
}
} else if (parameter.getSchema() != null) {
parameterSchema = unaliasSchema(parameter.getSchema());
parameterModelName = getParameterDataType(parameter, parameterSchema);
CodegenProperty prop;
Expand Down Expand Up @@ -5688,6 +5743,9 @@ public CodegenParameter fromParameter(Parameter parameter, Set<String> imports)
codegenParameter.isHeaderParam = true;
} else if (parameter instanceof CookieParameter || "cookie".equalsIgnoreCase(parameter.getIn())) {
codegenParameter.isCookieParam = true;
} else if (parameter instanceof QueryStringParameter || "querystring".equalsIgnoreCase(parameter.getIn())) {
// OpenAPI 3.2: the parameter describes the entire query string via `content`
codegenParameter.isQueryStringParam = true;
} else {
LOGGER.warn("Unknown parameter type: {}", parameter.getName());
}
Expand Down Expand Up @@ -5935,6 +5993,19 @@ public boolean isDataTypeBinary(String dataType) {
}
}

/**
* Whether this generator emits dedicated serialization for OpenAPI 3.2
* {@code in: querystring} parameters (the parameter value is the whole,
* already-encoded query string rather than a single name=value pair).
* Generators without support may drop the parameter or emit a named query
* parameter - {@link #fromParameter} warns in that case.
*
* @return true if the generator supports {@code in: querystring} parameters
*/
protected boolean supportsQueryStringParameters() {
return false;
}

// TODO revise below as it should be replaced by ModelUtils.isFileSchema(parameterSchema)
public boolean isDataTypeFile(String dataType) {
if (dataType != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ void configureGeneratorProperties() {
// resolve inline models
if (config.getUseInlineModelResolver()) {
InlineModelResolver inlineModelResolver = new InlineModelResolver();
inlineModelResolver.setCodegen(config);
inlineModelResolver.setInlineSchemaNameMapping(config.inlineSchemaNameMapping());
inlineModelResolver.setInlineSchemaOptions(config.inlineSchemaOption());

Expand Down Expand Up @@ -1497,6 +1498,15 @@ public Map<String, List<CodegenOperation>> processPaths(Paths paths) {
processOperation(resourcePath, "patch", path.getPatch(), ops, path);
processOperation(resourcePath, "options", path.getOptions(), ops, path);
processOperation(resourcePath, "trace", path.getTrace(), ops, path);
if (config.supportsAdditionalOperations()) {
processOperation(resourcePath, "query", path.getQuery(), ops, path);
if (path.getAdditionalOperations() != null) {
path.getAdditionalOperations().forEach((method, operation) ->
processOperation(resourcePath, method, method, operation, ops, path));
}
} else if (hasQueryOrAdditionalOperations(path)) {
LOGGER.warn("Path '{}' declares OpenAPI 3.2 query/additionalOperations but generator '{}' does not support them; those operations will be missing from the generated output", resourcePath, config.getName());
}
}
return ops;
}
Expand All @@ -1518,11 +1528,29 @@ public Map<String, List<CodegenOperation>> processWebhooks(Map<String, PathItem>
processOperation(resourceKey, "patch", path.getPatch(), ops, path);
processOperation(resourceKey, "options", path.getOptions(), ops, path);
processOperation(resourceKey, "trace", path.getTrace(), ops, path);
if (config.supportsAdditionalOperations()) {
processOperation(resourceKey, "query", path.getQuery(), ops, path);
if (path.getAdditionalOperations() != null) {
path.getAdditionalOperations().forEach((method, operation) ->
processOperation(resourceKey, method, method, operation, ops, path));
}
} else if (hasQueryOrAdditionalOperations(path)) {
LOGGER.warn("Webhook '{}' declares OpenAPI 3.2 query/additionalOperations but generator '{}' does not support them; those operations will be missing from the generated output", resourceKey, config.getName());
}
}
return ops;
}

private boolean hasQueryOrAdditionalOperations(PathItem path) {
return path.getQuery() != null
|| (path.getAdditionalOperations() != null && !path.getAdditionalOperations().isEmpty());
}

private void processOperation(String resourcePath, String httpMethod, Operation operation, Map<String, List<CodegenOperation>> operations, PathItem path) {
processOperation(resourcePath, httpMethod, null, operation, operations, path);
}

private void processOperation(String resourcePath, String httpMethod, String wireHttpMethod, Operation operation, Map<String, List<CodegenOperation>> operations, PathItem path) {
if (operation == null) {
return;
}
Expand All @@ -1533,7 +1561,7 @@ private void processOperation(String resourcePath, String httpMethod, Operation
List<Operation> contentTypeVariants = config.divideOperationsByContentType(openAPI, resourcePath, httpMethod, operation);
if (contentTypeVariants.size() > 1) {
for (Operation variant : contentTypeVariants) {
processOperation(resourcePath, httpMethod, variant, operations, path);
processOperation(resourcePath, httpMethod, wireHttpMethod, variant, operations, path);
}
return;
}
Expand Down Expand Up @@ -1604,6 +1632,11 @@ private void processOperation(String resourcePath, String httpMethod, Operation
httpMethod, resourcePath, operation.getOperationId());
} else {
CodegenOperation codegenOperation = config.fromOperation(resourcePath, httpMethod, operation, path.getServers());
if (wireHttpMethod != null) {
// OpenAPI 3.2 additionalOperations: the map key is the HTTP method
// name and must be sent verbatim instead of upper-cased
codegenOperation.httpMethod = wireHttpMethod;
}
codegenOperation.tags = new ArrayList<>(tags);
config.addOperationToGroup(config.sanitizeTag(tag.getName()), resourcePath, operation, codegenOperation, operations);

Expand Down
Loading