Skip to content

[BUG][RUBY-NEXTGEN] Invalid method names from path segments such as {id}!preview, {id}:archive or export.csv #24998

Description

@wiebren

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions