Skip to content

genopenapi: Stable openapi names for nested types #3917

Closed
@jgiles

Description

🚀 Feature

Summary

An option for output similar to the current openapi_naming_strategy=simple output but unconditionally qualifying nested type names rather than only doing so dynamically upon collision. This could be a modification of the behavior of the "simple" output (if the current behavior is viewed as a bug), or it could be a new option.

Current Behavior

Currently, strategies other than fqn dynamically determine how to qualify type names based on detecting collisions. This means that the generated names for a given type can change as new types are added to the genopapi invocation scope.

Current Behavior Example

Before File

Simple service getting entities Foo. Foo has a State.

syntax = "proto3";
package example.package;
option go_package = "github.com/example/packagepb";
import "google/api/annotations.proto";

service FooService {
    rpc GetFoo (GetFooRequest) returns (Foo) {
        option (google.api.http) = {
            get: "/foo"
        };
    }
}

message GetFooRequest {}

message Foo {
    enum State {
        STATUS_UNKNOWN = 0;
        PENDING = 1;
        FOOED = 2;
    }
    State state = 1;
}

After File

We add an entity Bar that also has a State in the same proto package.

syntax = "proto3";
package example.package;
option go_package = "github.com/example/packagepb";
import "google/api/annotations.proto";

service FooService {
    rpc GetFoo (GetFooRequest) returns (Foo) {
        option (google.api.http) = {
            get: "/foo"
        };
    }
}

message GetFooRequest {}

message Foo {
    enum State {
        STATUS_UNKNOWN = 0;
        PENDING = 1;
        FOOED = 2;
    }
    State state = 1;
}

service BarService {
    rpc GetBar (GetBarRequest) returns (Bar) {
        option (google.api.http) = {
            get: "/bar"
        };
    }
}

message GetBarRequest {}

message Bar {
    enum State {
        STATUS_UNKNOWN = 0;
        PENDING = 1;
        BARRED = 2;
    }
    State state = 1;
}

Emitted Type Names With Each openapi_naming_strategy

  • legacy
    • Before: FooState
    • After: packageFooState, packageBarState
    • Notes: Adding another type changed the existing type's name, and the generated names in "after" are unnecessarily qualified with package
  • fqn
    • Before: example.package.Foo.State
    • After: example.package.Foo.State, example.package.Bar.State
    • Notes: The output is predictable and stable but overly verbose for most use cases.
  • simple
    • Before: State
    • After: Foo.State, Bar.State
    • Notes: Adding another type changed the existing type's name. In the "Before" name, the generic "State" is not very meaningful - the name is meant to be meaningful in the context of Foo. The After output is desirable for that case (clear, no collisions, not overly verbose).

Desired Behavior

Always output Foo.State for the nested type (unless Foo.State collides with a name from another package, in which case qualify it like package.Foo.State). This is like in the "after" output from the simple strategy, but it would not depend on whether another type Bar.State happened to be declared in the package.

In general, it would be nice to have options for more predictable and explicit control of name behavior in OpenAPI generation, rather than trying to "fix" collisions automatically as they are discovered at generation time. That is a larger issue, because there are complexities when colliding types are pulled in from other packages (I use State in this example because Status collides with the common protobuf Status type, which complicates the example). However, within a given package we should be able to mirror how nested-type names are generated in other language plugins (by qualifying the nested types), and as long as we use a separator that cannot appear in the proto type names themselves we will not have collisions within the package (see #1066 for an issue where qualification without separators can still result in collisions).

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