Skip to content

Conversation

@rdark
Copy link

@rdark rdark commented Nov 6, 2025

Bug: Nested Required Fields Incorrectly Hoisted to Parent Body Schema

Summary

protoc-gen-openapiv2 incorrectly hoists nested message field names marked as REQUIRED into the parent request body's required array when those fields are also used in path parameters. This creates invalid OpenAPI schemas where the required array references field names that don't exist at the top level.

Version

  • grpc-gateway v2.27.3 (also affects earlier versions)

Prerequisites

The bug occurs when ALL these conditions are met:

  1. Update RPC with a request message containing a nested message field
  2. Path parameter extraction: At least one field from the nested message is used in the URL path (e.g., {thing.name})
  3. REQUIRED annotations: The nested message has fields marked with (google.api.field_behavior) = REQUIRED
  4. Body parameter generation: The RPC uses body: "*" or similar

Example

Proto Definition

message Foo {
  string name = 1 [(google.api.field_behavior) = REQUIRED];
  string value = 2 [(google.api.field_behavior) = REQUIRED];
}

message UpdateFooRequest {
  Foo thing = 1 [(google.api.field_behavior) = REQUIRED];
  google.protobuf.FieldMask update_mask = 2;
}

service FooService {
  rpc UpdateFoo(UpdateFooRequest) returns (UpdateFooResponse) {
    option (google.api.http) = {
      patch: "/api/v1/{thing.name}"
      body: "*"
    };
  }
}

Current (Incorrect) Output

{
  "definitions": {
    "FooServiceUpdateFooBody": {
      "type": "object",
      "properties": {
        "thing": {
          "type": "object",
          "properties": {
            "value": { "type": "string" }
          }
        },
        "updateMask": { "type": "string" }
      },
      "required": [
        "value",      // WRONG: "value" is nested inside "thing"!
        "thing"
      ]
    }
  }
}

Expected (Correct) Output

{
  "definitions": {
    "FooServiceUpdateFooBody": {
      "type": "object",
      "properties": {
        "thing": {
          "type": "object",
          "properties": {
            "value": { "type": "string" }
          },
          "required": ["value"]  // Nested required stays here
        },
        "updateMask": { "type": "string" }
      },
      "required": ["thing"]  // Only top-level fields
    }
  }
}

Root Cause

In protoc-gen-openapiv2/internal/genopenapi/template.go (around line 609-615), when processing nested message fields with path parameters, ALL required field names from the nested schema are hoisted to the parent's required array without distinguishing between:

  • The field name itself (which should be in parent's required if marked REQUIRED)
  • Nested field names within that field (which should stay in nested schema)

References to other Issues or PRs

possibly originated in #2635

Have you read the Contributing Guidelines?

Brief description of what is fixed or changed

Other comments

When generating OpenAPI schemas for request bodies with path parameters
extracted from nested messages (e.g., {thing.name}), nested required
field names were incorrectly added to the parent body schemas required
array instead of remaining in the nested objects required array.

For example, with UpdateFooRequest containing a required Foo field, and
Foo having required name and value fields, when using path parameter
{thing.name}, the generated body schema incorrectly had:
  required: ["value", "thing"]
Instead of:
  required: ["thing"] with thing.required: ["value"]

This fix adds logic to distinguish between field-level and nested-level
required markers when path parameters are present. It ensures only the
field name itself appears in the parents required array if the field
is marked REQUIRED, while nested field names remain in the nested
schemas required array.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant