Skip to content

Positional path-arg order follows OpenAPI parameters array, not the URL path — wrong target on multi-param routes #61

Description

@barberscott

Summary

For operations with more than one path parameter, the generated command lists (and consumes) its positional arguments in the order they appear in the spec's parameters array, rather than the order the {placeholders} appear in the URL path. When a spec lists those params in a different order than the path (which OpenAPI permits — array order is not significant), the CLI's positional arg order is reversed relative to the path. Users who pass args in the natural path order silently hit the wrong resource (or a 404).

Example

Route: GET /api/v2/documents/{identifier}/draft/{draftIdentifier}

Generated usage:

omni documents v2-get-draft <draftidentifier> <identifier>

The path has {identifier} first, but the command asks for <draftidentifier> first.

# Natural call — args in path order (document, then draft):
omni documents v2-get-draft <doc-id> <draft-id>
# -> 404: substitutes {draftIdentifier}=<doc-id>, {identifier}=<draft-id>

# Only works if you invert to match the printed (reversed) usage:
omni documents v2-get-draft <draft-id> <doc-id>   # works

Same reversal affects documents v2-patch-draft-by-identifier — and there it's higher-risk: if both ids happen to be valid documents, a PATCH could mutate the wrong document with no error.

Root cause

  • Usage string is built from op.PathParams in spec-array order — internal/openapi/generate.go (~L163).
  • cobra.ExactArgs + substitution then consume args in that same array order (~L186).
  • Substitution itself is correct (by-name), so following the printed (reversed) usage works — but the order contradicts the path, which is the trap.

Per OpenAPI, parameters array order carries no meaning; path params must be resolved by name/position-in-path. The generator shouldn't depend on array order.

Suggested fix

Order op.PathParams by the index of each {name} within op.Path before building the usage string and the substitution loop. Sketch:

sort.SliceStable(op.PathParams, func(i, j int) bool {
    return strings.Index(op.Path, "{"+op.PathParams[i].Name+"}") <
           strings.Index(op.Path, "{"+op.PathParams[j].Name+"}")
})

This makes both the usage and the request robust regardless of how the spec orders its parameters array. (Single-path-param commands — the vast majority — are unaffected.)

Repro environment

Found while smoke-testing a from-source build against the v2 documents routes; confirmed v2-get-draft returns the correct draft only when args are passed in the reversed (printed) order.

Filed by Claude on behalf of @barberscott.

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