Skip to content

Name clashes when creating open api doc #3160

@jeffque

Description

@jeffque

Describe the bug

When describing an inner record with a name that clashes with a package level record, the openapi description clashes both names and describe something that is not compatible with the API.

To Reproduce

Create a record name Data with an inner record named Value with a schema, then create a record at package level named Value with another schema. In the controller, return both com.example.springboot.Data and com.example.springboot.Value:

Records:

package com.example.springboot;

public record Value(String internalValue) {
}
package com.example.springboot;

public record Data(String key, Value value) {
    public record Value(String id, String value) {}
}

Controller:

package com.example.springboot;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

	@GetMapping("/data")
	public Data data() {
		return new Data("key", new Data.Value("id", "value"));
	}

	@GetMapping("/value")
	public Value value() {
		return new Value("this is a value");
	}
}

This repo was created to easily reproduce this problem https://github.com/jeffque/spring-doc-name-clash

Using:

  • spring-boot 4.0.0 (reproducible with 3.2.9)

  • springdoc-openapi 3.0.0 (reproducible with 2.5.0)

  • What is the actual and the expected result using OpenAPI Description (yml or json)?

Obtained openapi description:

{
    "openapi": "3.1.0",
    "info": {
        "title": "OpenAPI definition",
        "version": "v0"
    },
    "servers": [
        {
            "url": "http://localhost:8080",
            "description": "Generated server url"
        }
    ],
    "paths": {
        "/value": {
            "get": {
                "tags": [
                    "hello-controller"
                ],
                "operationId": "value",
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "*/*": {
                                "schema": {
                                    "$ref": "#/components/schemas/Value"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/data": {
            "get": {
                "tags": [
                    "hello-controller"
                ],
                "operationId": "data",
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "*/*": {
                                "schema": {
                                    "$ref": "#/components/schemas/Data"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "Value": {
                "type": "object",
                "properties": {
                    "internalValue": {
                        "type": "string"
                    }
                }
            },
            "Data": {
                "type": "object",
                "properties": {
                    "key": {
                        "type": "string"
                    },
                    "value": {
                        "$ref": "#/components/schemas/Value"
                    }
                }
            }
        }
    }
}

Expected behavior

The Value type description mentioned by the schema Data is pointing towards a description that is not compatible with the used type. What was expected was that it pointed towards another schema, for example:

{
    ...
    "components": {
        "schemas": {
            "Value": {
                "type": "object",
                "properties": {
                    "internalValue": {
                        "type": "string"
                    }
                }
            },
            "Data": {
                "type": "object",
                "properties": {
                    "key": {
                        "type": "string"
                    },
                    "value": {
                        "$ref": "#/components/schemas/Data.Value"
                    }
                }
            },
            "Data.Value": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "value": {
                        "type": "string"
                    }
                }
            }
        }
    }
}

This prevents name clash between the inner record com.example.springboot.Data.Value and the package level record com.example.springboot.Value.

This is not expected to occur only with records, but it should happen too with common classes.

Additional context

There are workarounds regarding this, like renaming the inner record in a way that its name does not clash with other classes. But this is intrusive and non-intuitive, sometimes the records are named in a way that is semantically accurate and renaming all the clashes is not ideal.

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