Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested fields in ParameterObject do not take into account annotations on parent classes #2787

Open
mc1arke opened this issue Nov 24, 2024 · 0 comments

Comments

@mc1arke
Copy link
Contributor

mc1arke commented Nov 24, 2024

Describe the bug

For a @ParameterObject annotated value, any annotations on the fields of the target class are not taken into account when creating the flattened values for the parameters. I believe this leads to 3 types or incorrect definitions being produced:

Hidden parent fields

When a field in the @ParameterObject class is defined as hidden, or any subfields are hidden, those fields and any child fields should not included in the generated schema.

For the following endpoint definition and model

    @GetMapping("/hidden-parent")
    public void nestedParameterObjectWithHiddenParentField(@ParameterObject ParameterObjectWithHiddenField parameters) {

    }

    public record ParameterObjectWithHiddenField(
        @Schema(hidden = true) NestedParameterObject schemaHiddenNestedParameterObject,
        @Parameter(hidden = true) NestedParameterObject parameterHiddenNestedParameterObject,
        NestedParameterObject visibleNestedParameterObject
    ) {

    }

    public record NestedParameterObject(
        String parameterField) {
    }

I'd expect the schemaHiddenNestedParameterObject and parameterHiddenNestedParameterObject fields, plus any child fields, to be excluded from the generated result, e.g.

      "parameters": [
          {
            "name": "visibleNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ]

but they're currently included, e.g.

      "parameters": [
          {
            "name": "schemaHiddenNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parameterHiddenNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "visibleNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ]

Renamed parent fields

Where a field in the @ParameterObject is specifically given a name through an appropriate annotation, this name should be applied in the flattened definition, but the field name as defined in the code is currently used.

For the following endpoint definition and model

    @GetMapping("/renamed-parent")
    public void nestedParameterObjectWithRenamedParentField(@ParameterObject ParameterObjectWithRenamedField parameters) {

    }

    public record ParameterObjectWithRenamedField(
        @Schema(name = "schemaRenamed") NestedParameterObject schemaRenamedNestedParameterObject,
        @Parameter(name = "parameterRenamed") NestedParameterObject parameterRenamedNestedParameterObject,
        NestedParameterObject originalNameNestedParameterObject
    ) {

    }

    public record NestedParameterObject(
        String parameterField) {
    }

I'd expect the schemaRenamedNestedParameterObject and parameterRenamedNestedParameterObject to be given the names from the annotation in the resulting definition, e.g.

        "parameters": [
          {
            "name": "schemaRenamed.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parameterRenamed.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "originalNameNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ]

but the original field names are used, e.g.

        "parameters": [
          {
            "name": "schemaRenamedNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parameterRenamedNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "originalNameNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ]

Mandatory parameters in non-mandatory parent fields

Where a fields is marked as required (or resolves as required for a RequiredMode.AUTO annotation), but the parent fields is not required, that child field would not be mandatory within the request. There is a challenge here that the field would be required if any of the other fields from the parent are set, but there's no way of defining that in a flattened structure, so the definition should default to not forcing a field that's only conditionally required.

Given the following endpoint and model

    @GetMapping("/optional-parent")
    public void nestedParameterObjectWithOptionalParentField(@ParameterObject ParameterObjectWithOptionalField parameters) {

    }

    public record ParameterObjectWithOptionalField(
        @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED) NestedRequiredParameterObject schemaNotRequiredNestedParameterObject,
        @Parameter NestedRequiredParameterObject parameterNotRequiredNestedParameterObject,
        @Parameter(required = true) NestedRequiredParameterObject requiredNestedParameterObject
    ) {

    }

    public record NestedRequiredParameterObject(
        @Schema(requiredMode = Schema.RequiredMode.REQUIRED) String requiredParameterField) {
    }

I'd expect the fields makes as REQUIRED on an object that isn't set as REQUIRED to be shown as not required in the flattened definition, e.g.

        "parameters": [
          {
            "name": "schemaNotRequiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parameterNotRequiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "requiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]

but the definition currently only takes into account the child element annotations to treats the fields as required, e.g.

        "parameters": [
          {
            "name": "schemaNotRequiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parameterNotRequiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "requiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]

Additional Information

  • The version of Spring Boot being used does not seem to have any impact. The above examples were generated using 3.4.0 and the latest SNAPSHOT of springdoc-openapi from e8de90e on the main branch.
@mc1arke mc1arke changed the title Nested fields in ParameterObject to not take into account annotations on parent classes Nested fields in ParameterObject do not take into account annotations on parent classes Nov 24, 2024
mc1arke added a commit to mc1arke/springdoc-openapi that referenced this issue Nov 24, 2024
…doc#2787

When creating the flattened parameter definitions for an item annotated
with `@ParameterObject`, the `@Schema` and `@Property` annotations on
any fields prior to the final child-node of each branch of the parameter
tree are not taken into consideration. This results in the field name in
the code being used even where annotations may override that, prevents
the fields being hidden where one of the parent fields is marked as
hidden but the child field isn't hidden, and marks a field as mandatory
even where the field would only be mandatory if the parent object had
been declared through a sibling of the target field being set. To
overcome this, whilst the flattened parameter map is being built, each
field is now being inspected for a `@Parameter` annotation or - where
the `@Parameter` annotation isn't found - a `@Schema` annotation. Where
a custom name is included in either of those annotations then the
overridden field name is used in the flattened definition. If either
annotation declares the field as hidden then the field and all child
fields are removed from the resulting definition, and a field is no
longer set as mandatory unless the parent field was also declared as
mandatory, or resolved as non-null and was set in an automatic state.
mc1arke added a commit to mc1arke/springdoc-openapi that referenced this issue Nov 24, 2024
…doc#2787

When creating the flattened parameter definitions for an item annotated
with `@ParameterObject`, the `@Schema` and `@Property` annotations on
any fields prior to the final child-node of each branch of the parameter
tree are not taken into consideration. This results in the field name in
the code being used even where annotations may override that, prevents
the fields being hidden where one of the parent fields is marked as
hidden but the child field isn't hidden, and marks a field as mandatory
even where the field would only be mandatory if the parent object had
been declared through a sibling of the target field being set. To
overcome this, whilst the flattened parameter map is being built, each
field is now being inspected for a `@Parameter` annotation or - where
the `@Parameter` annotation isn't found - a `@Schema` annotation. Where
a custom name is included in either of those annotations then the
overridden field name is used in the flattened definition. If either
annotation declares the field as hidden then the field and all child
fields are removed from the resulting definition, and a field is no
longer set as mandatory unless the parent field was also declared as
mandatory, or resolved as non-null and was set in an automatic state. To
ensure that the post-processing steps don't re-apply `required`
properties on parameters that have purposefully had them removed, the
delegate now tracks any annotations what should not be shown as being on
the parameter, and excludes them from the annotation list during
subsequent calls.
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

No branches or pull requests

1 participant