Bug Report Checklist
Description
ruby-nextgen names an operation's method after the literal path segments (RubyApiRouting.deriveAction, step b), unless the operationId starts with the group name (thingsPreview → preview, step a). Any other operationId is ignored. Step (b) runs each literal segment through underscore() but doesn't sanitize it, so a segment that is valid in a URL but not in a Ruby identifier becomes an invalid def:
| path |
method |
/things/{id}!preview |
def {id}!preview(id:) |
/things/{id}:archive |
def {id}:archive(id:) |
/things/{id}/export.csv |
def export/csv(id:) (underscore turns . into /) |
/things/{id}/re-index |
def re_index(id:) (fine) |
A segment such as {id}!preview is not treated as a parameter because it doesn't end in }, so the placeholder ends up in the name. A single such path makes the whole api file fail to parse, so every operation in that group is lost.
openapi-generator version
master, 7.26.0-SNAPSHOT, 05b61f34d7fb0199330e1d6c57e6159f72427837
OpenAPI declaration file content or url
openapi: 3.0.3
info:
title: names
version: 1.0.0
paths:
/things:
get:
operationId: listThings
responses:
'200':
description: OK
/things/{id}!preview:
get:
operationId: previewThing
parameters:
- $ref: '#/components/parameters/id'
responses:
'200':
description: OK
/things/{id}/re-index:
post:
operationId: reindexThing
parameters:
- $ref: '#/components/parameters/id'
responses:
'204':
description: No content
/things/{id}/export.csv:
get:
operationId: exportThing
parameters:
- $ref: '#/components/parameters/id'
responses:
'200':
description: OK
/things/{id}:archive:
post:
operationId: archiveThing
parameters:
- $ref: '#/components/parameters/id'
responses:
'204':
description: No content
components:
parameters:
id:
name: id
in: path
required: true
schema:
type: string
Generation Details
java -jar openapi-generator-cli.jar generate -g ruby-nextgen -i spec.yaml -o out \
--additional-properties=gemName=petstore,moduleName=Petstore
Generation succeeds without warnings. Generated lib/petstore/api/things.rb (method heads):
def export/csv(id:)
def list
def re_index(id:)
def {id}!preview(id:)
def {id}:archive(id:)
Steps to reproduce
ruby -c out/lib/petstore/api/things.rb
Actual (ruby 3.3):
out/lib/petstore/api/things.rb:10: syntax error, unexpected '/', expecting ';' or '\n' (SyntaxError)
def export/csv(id:)
out/lib/petstore/api/things.rb:43: syntax error, unexpected {
def {id}!preview(id:)
out/lib/petstore/api/things.rb:55: syntax error, unexpected {
def {id}:archive(id:)
Petstore::Client.new(base_url: 'http://localhost').things fails with the same SyntaxError when Zeitwerk loads the file.
Expected: valid method names, e.g. preview, archive, export_csv, re_index.
Related issues/PRs
None found.
Suggest a fix
In step (b), remove {param} placeholders and replace non-identifier characters before underscore():
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/rubynextgen/RubyApiRouting.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/rubynextgen/RubyApiRouting.java
@@ -154,8 +154,11 @@ public final class RubyApiRouting {
if (actionStart < lits.size()) {
StringBuilder sb = new StringBuilder();
for (int i = actionStart; i < lits.size(); i++) {
+ String lit = lits.get(i).replaceAll("\\{[^}]*\\}", "").replaceAll("[^A-Za-z0-9_]+", "_");
+ lit = lit.replaceAll("^_+|_+$", "");
+ if (lit.isEmpty()) continue;
if (sb.length() > 0) sb.append("_");
- sb.append(underscore(lits.get(i)));
+ sb.append(underscore(lit));
}
return sb.toString();
}
With this change the methods become archive, export_csv, list, preview and re_index, and the file loads. CrystalApiRouting has the same loop (not checked against the crystal output).
Generated with Claude Code
Bug Report Checklist
Description
ruby-nextgennames an operation's method after the literal path segments (RubyApiRouting.deriveAction, step b), unless theoperationIdstarts with the group name (thingsPreview→preview, step a). Any otheroperationIdis ignored. Step (b) runs each literal segment throughunderscore()but doesn't sanitize it, so a segment that is valid in a URL but not in a Ruby identifier becomes an invaliddef:/things/{id}!previewdef {id}!preview(id:)/things/{id}:archivedef {id}:archive(id:)/things/{id}/export.csvdef export/csv(id:)(underscoreturns.into/)/things/{id}/re-indexdef re_index(id:)(fine)A segment such as
{id}!previewis not treated as a parameter because it doesn't end in}, so the placeholder ends up in the name. A single such path makes the whole api file fail to parse, so every operation in that group is lost.openapi-generator version
master, 7.26.0-SNAPSHOT,
05b61f34d7fb0199330e1d6c57e6159f72427837OpenAPI declaration file content or url
Generation Details
Generation succeeds without warnings. Generated
lib/petstore/api/things.rb(method heads):Steps to reproduce
Actual (ruby 3.3):
Petstore::Client.new(base_url: 'http://localhost').thingsfails with the sameSyntaxErrorwhen Zeitwerk loads the file.Expected: valid method names, e.g.
preview,archive,export_csv,re_index.Related issues/PRs
None found.
Suggest a fix
In step (b), remove
{param}placeholders and replace non-identifier characters beforeunderscore():With this change the methods become
archive,export_csv,list,previewandre_index, and the file loads.CrystalApiRoutinghas the same loop (not checked against the crystal output).Generated with Claude Code