Skip to content

Conversation

lfec
Copy link

@lfec lfec commented Dec 10, 2024

Add Support for External Example Configuration in openapi-maven-plugin

Description

This Pull Request introduces the ability to add Examples to OpenAPI entities, parameters, and operations. The examples can be specified via an external file provided in the Maven plugin configuration. This feature enhances the plugin by allowing developers to maintain and manage examples separately from the codebase.
This is also mentioned in #79 and #50

Key Features:

  • New Configuration Option:

    • A new configuration parameter has been added to the Maven pom.xml, allowing the user to specify the path to an external file containing examples.
  • Supported Example Structure:

    • The external file should follow the JSON structure below:
      {
        "PARAMETER": {},
        "SCHEMA": {},
        "REQUESTBODY": {},
        "RESPONSE": {}
      }
    • SCHEMA: Uses entity names as keys to assign examples.
    • PARAMETER, REQUESTBODY, RESPONSE: Use the operationId as the key to define examples.
  • Code First Approach: This feature aligns with the plugin's philosophy of maintaining documentation through code-first principles, while offering flexibility for managing external examples.

How to Use

  1. Add the new configuration to the Maven pom.xml:
    <configuration>
        <customExamples>src\main\webapp\openapi\examples.json</customExamples>
    </configuration>
    
  2. Create the external file using the prescribed JSON structure.
  3. Run the Maven goal as usual, and the plugin will automatically incorporate the examples into the generated OpenAPI schema.

Example

Below are examples of how the configuration can be used for each module: PARAMETER, SCHEMA, REQUESTBODY, and RESPONSE.


1. Parameter Examples

The example below demonstrates how this configuration supports both Path parameters and Header parameters:

  {
    "PARAMETER": {
      "getParameter": {
        "paramField": "Path Variable Parameter Example",
        "headerField": "Request Header Parameter Example"
      }
    }
  }

2. Schema Examples

In this configuration, ExampleEnum and ExampleRequestBodyDto represent the names of entities. This demonstrates that the feature works for both simple entities (e.g., enums) and complex objects (e.g., DTOs with nested properties)

  {
    "SCHEMA": {
      "ExampleEnum": "EXAMPLE_A",
      "ExampleRequestBodyDto": {
        "requestEnumList": ["EXAMPLE_A", "EXAMPLE_C"],
        "requestName": "example of a SCHEMA Request Name",
        "requestValue": "example of a SCHEMA Request Value"
      }
    }
  }

3. Request Body Examples

For the REQUESTBODY module, the configuration allows both a default example and multiple options that OpenAPI supports. When using multiple options, the JSON should be formatted as an array to allow readers to choose during testing

  {
    "REQUESTBODY": {
      "postExample": [
        {
          "OPTION_A": {
            "value": {
              "requestEnumList": ["EXAMPLE_A", "EXAMPLE_A"],
              "requestName": "example of an OPTION A Request Name",
              "requestValue": "example of an OPTION A Request Value"
            }
          },
          "OPTION_B": {
            "value": {
              "requestEnumList": ["EXAMPLE_B", "EXAMPLE_B"],
              "requestName": "example of an OPTION B Request Name",
              "requestValue": "example of an OPTION B Request Value"
            }
          }
        }
      ]
    }
  }

4. Response Examples

The following example demonstrates how to configure RESPONSE examples.

  {
    "RESPONSE": {
      "postExample": "Example of a String value return",
      "getParameter": {
        "responseEnum": "EXAMPLE_C",
        "responseName": "example of a RESPONSE Response Name",
        "responseValue": "example of a RESPONSE Response Value"
      },
      "getHeader": {
        "$ref": "#/components/examples/ResponseExample"
      }
    }
  }

BOTH Request Body and Response supports Multi Options and referenced examples

To use the $ref the respective information must be informed using Free Fields

@kbuntrock
Copy link
Owner

Thank you very much @lfec for this huge PR. I greatly appreciate the detailed explanations about this new feature.
I'll have a closer look at it this weekend.

lfec added 2 commits May 9, 2025 15:18
# Conflicts:
#	openapi-maven-plugin/src/main/java/io/github/kbuntrock/yaml/YamlWriter.java
#	openapi-maven-plugin/src/main/java/io/github/kbuntrock/yaml/model/Content.java
#	openapi-maven-plugin/src/test/java/io/github/kbuntrock/SpringClassAnalyserTest.java
Copy link
Owner

@kbuntrock kbuntrock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, thank you very much @lfec for your tremendous patience!

I read your PR, looks pretty good to me. A big shout out for this work!

I have a few remarks about it. I have posted them with the code when it was appropriate, and I post here the more global remarks:

  1. PARAMETER, REQUESTBODY and RESPONSE use the operationId as the key. Therefore, it looks more straightforward to me to organise it around the operationId key.
  2. Request body and response can differ regarding the content-type. I would like to introduce a new hierarchic level and the "default" keyword for examples which should be applied for all content-types.
  3. The array for multiple example options seems unused since you use only the first element and then put a map in it (OPTION_A, OPTION_B). I suggest to remove the array and apply a different treatment if the node contains a single element or not.
    -> if single node : declaring an "example" node, and the name of the example is not used in the specification
    -> if multiple : declaring an "examples" node, the names of the examples are kept
  4. Parameters seems also to offer multiple examples, we should support this.

So, regarding to the 3 first points, here is an example of the new structure I suggest:

{
  "OPERATIONS" : [
	"operationId_1" : {
	  "PARAMETER": {
	    "first_parameter_name" {
		  "example_name": { // Here the name of the example will not be used in the documentation since it is alone
		  
		  }
		},
		"second_parameter_name" {
		  "example_name_1": {
		  
		  },
		  "example_name_2": {
		  
		  },
		}
	  },
	  "REQUESTBODY": {
	    "default": {
		  "example_name": { // Here the name of the example will not be used in the documentation since it is alone
		  
		  }
		}
	  },
	  "RESPONSE": {
		"application/json": {
		  "example_name": { // Here the name of the example will not be used in the documentation since it is alone
		  
		  }
		},
		"application/xml": {
		  "example_name_1": {
		  
		  },
		  "example_name_2": {
		  
		  }
		}
	  }
	},
	"operationId_2" : {
	  "PARAMETER": {},
	  "REQUESTBODY": {},
	  "RESPONSE": {}
	}
  ],
  "SCHEMA": {}
}

Regarding the 4th point, I suggest to change the positioning of the example outside the parameters schema in order to switch more easily between a mono/multiple examples.

Which would lead to this kind of generated doc snippet:

parameters:
  - name: paramField
    in: path
    required: true
    schema:
      type: string
    examples: 
      example1:
        value: value example one
      example2: 
        value: value example two
  - name: headerField
    in: header
    required: true
    schema:
      type: string
    example: header example value

So what do you think about these suggestions? :)

},
"components": {
"examples": {
"ResponseExample": {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small change in order to make it a valid example regarding the specification:

Suggested change
"ResponseExample": {
"ResponseExample": {
"value": {
"responseEnum": "EXAMPLE_C",
"responseName": "example of a REFERENCE Response Name",
"responseValue": "example of a REFERENCE Response Value"
}
}

Current warning (not reported in the plugin) is :
This is not a valid example object : "Structural error at components.examples.ResponseExample should NOT have additional properties additionalProperty: responseEnum, responseName, responseValue"

@@ -7,7 +7,7 @@

<groupId>io.github.kbuntrock</groupId>
<artifactId>openapi-maven-plugin</artifactId>
<version>0.0.24-SNAPSHOT</version>
<version>0.0.22-EXAMPLES</version>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please discard this change when you are ready for a merge - in order to fix the integration tests.

@@ -102,6 +102,9 @@ public class CommonApiConfiguration {
@Parameter
protected String defaultErrors;

@Parameter
protected String customExamples;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to put a list here, so people can split / organise their examples in multiple files.

And for the merge feature in the ApiConfiguration with a list, just replace the list, there is no need for too much sophistication.

@@ -130,6 +134,11 @@ public void write(final File file, final TagLibrary tagLibrary) throws IOExcepti
iterator.forEachRemaining(entry -> defaultErrors.put(entry.getKey(), entry.getValue()));
}

final Optional<JsonNode> customExamplesNode = JsonParserUtils.parse(
CommonParserUtils.getContentFromFileOrText(mavenProject, apiConfiguration.getCustomExamples()));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea for a next iteration: allowing folders in example configuration. If so, we'll parse every files in it.

requestBodycustomExamples = new LinkedHashMap<>();
responseCustomExamples = new LinkedHashMap<>();

JsonNode parameterNode = customExamplesNode.get().get("PARAMETER");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is no other "all caps" convention in this project, I would prefer if we use lowercase or accept both lowercase and uppercase.

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.

2 participants