OpenAPITools / OpenAPITools/openapi-generator
[BUG] [JAVA] validateJsonElement() method doesn't respect `anyOf + oneOf + required` restriction along with `properties`
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 26.8k
- Forks
- 7.7k
- PR merge metrics
- PR metrics pending
Description
Bug Report Checklist
- Have you provided a full/minimal spec to reproduce the issue?
- Have you validated the input using an OpenAPI validator (example)?
- Have you tested with the latest master to confirm the issue still exists?
- Have you searched for related issues/PRs?
- What's the actual output vs expected output?
- [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
Was working on translating protobuf oneOf behavior to OpenAPI3.0 spec, which can be implemented using this representation: https://github.com/google/gnostic/issues/251#issuecomment-1088041728
But when I generated the code for the spec below, it generated Invalid validateJsonElement() method & openapiFields set
OpenAPI3.0 Spec with relevant protobuf model
// Represents either `id` or `name` or none of these fields should be present in Event.
message Event{
oneof identifier {
string id = 1;
string name = 2;
}
double event_number = 3;
string event_string = 4;
string organizer = 5;
}
oneOf (atmost one): id or name is allowed
{
"openapi": "3.0.3",
"info": {
"title": "Google Example Library API",
"description": "A simple Google Example Library API.",
"version": "v1"
},
"paths": {},
"servers": [
{
"url": "https://library-example.googleapis.com",
"description": "Global Endpoint"
}
],
"components": {
"schemas": {
"Event": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"event_number": {
"type": "number"
},
"event_string": {
"type": "string"
},
"organizer": {
"type": "string"
}
},
"anyOf": [
{
"oneOf": [ // either `id` or `name` is present
{
"required": [
"id"
]
},
{
"required": [
"name"
]
}
]
},
{
"not": { // none of `id` or `name` is present, oneOf in protobuf is atmost one, means both can be null
"required": [
"id",
"name"
]
}
}
],
"additionalProperties": false
}
}
}
,
"externalDocs": {
"description": "Find more info here.",
"url": "https://cloud.google.com/library/docs"
}
}
This spec generates below Java snippet: Event.java class
private String id;
private String name;
private BigDecimal eventNumber;
private String eventString;
private String organizer;
public static HashSet<String> openapiFields;
public static HashSet<String> openapiRequiredFields;
static {
// a set of all properties/fields (JSON key names)
openapiFields = new HashSet<String>();
// a set of required properties/fields (JSON key names)
openapiRequiredFields = new HashSet<String>();
}
/**
* Validates the JSON Element and throws an exception if issues found
*
* @param jsonElement JSON Element
* @throws IOException if the JSON Element is invalid with respect to BookEventDetails
*/
public static void validateJsonElement(JsonElement jsonElement) throws IOException {
if (jsonElement == null) {
if (!BookEventDetails.openapiRequiredFields.isEmpty()) { // has required fields but JSON element is null
.....
}
}
Set<Map.Entry<String, JsonElement>> entries = jsonElement.getAsJsonObject().entrySet();
// check to see if the JSON string contains additional fields
for (Map.Entry<String, JsonElement> entry : entries) {
if (!BookEventDetails.openapiFields.contains(entry.getKey())) {
// This snippet will fail due to empty openapiField in generated code.
throw new IllegalArgumentException(.....);
}
}
JsonObject jsonObj = jsonElement.getAsJsonObject();
if ((jsonObj.get("id") != null && !jsonObj.get("id").isJsonNull()) && !jsonObj.get("id").isJsonPrimitive()) {
throw new IllegalArgumentException(...);
}
if ((jsonObj.get("name") != null && !jsonObj.get("name").isJsonNull()) && !jsonObj.get("name").isJsonPrimitive()) {
throw new IllegalArgumentException(...);
}
if ((jsonObj.get("event_string") != null && !jsonObj.get("event_string").isJsonNull()) && !jsonObj.get("event_string").isJsonPrimitive()) {
throw new IllegalArgumentException(...);
}
if ((jsonObj.get("organizer") != null && !jsonObj.get("organizer").isJsonNull()) && !jsonObj.get("organizer").isJsonPrimitive()) {
throw new IllegalArgumentException(...);
}
}
Issues with the Above Codegen
Issue 1: Empty Set openapiFields. This issue is of type [BUG]
Expected below snippet foropenapiFields instead of empty set
static {
// a set of all properties/fields (JSON key names)
openapiFields = new HashSet<String>();
openapiFields.add("id");
openapiFields.add("name");
openapiFields.add("event_number");
openapiFields.add("event_string");
openapiFields.add("organizer");
// a set of required properties/fields (JSON key names)
openapiRequiredFields = new HashSet<String>();
}
Issue 2: generated validateJsonElement(jsonElement) method, doesn't respect the constraint represented by anyOf, oneOf, required keywords. This issue is more like a [Feature Request - Maybe tricky one with many edge cases]
Json Schema allOf/oneOf must be treated like an intersection on exiting properties.
sample json below:
{
"id": "event_123"
}
Json schema validator flow for above spec:
- Evaluating /components/schemas/Event/type keyword: is passed value has type object? Yes.
- Evaluating /components/schemas/Event/properties:
- Evaluating /components/schemas/Event/properties/id/type keyword: is passed value has type string? Yes.
- Evaluating /components/schemas/Event/anyOf keyword:
- Evaluating /components/schemas/Event/anyOf/0/OneOf:
- Evaluating /components/schemas/Event/anyOf/0/OneOf/0/required["id"]:: is passed value matches
id? Yes. - Evaluating /components/schemas/Event/anyOf/0/OneOf/1 -> skip
- Evaluating /components/schemas/Event/anyOf/1 -> skip
PASS!
.
.
.
Expection
Issue 1 can be fixed as a part of this bug, and issue 2 can be converted into separate FeatureRequest
openapi-generator version
7.0.1
Steps to reproduce
java -jar ./modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -i ./library_openapi_8.json -g java -o ~/Documents/library/libray_8
Suggest a fix
Yet to explore the code flow.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the issue with the provided java -jar generate command and the sample OpenAPI schema, then inspect the generated Event.java. Compare the empty openapiFields set and validateJsonElement behavior with the documented expected output. Done should at least resolve the openapiFields bug; the anyOf/oneOf/required behavior is identified as a separate, broader feature request.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100