From 1a613f2619fff42a084812078b65e96a0c59eb8f Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Thu, 9 Jan 2025 15:01:26 -0500 Subject: [PATCH] Implemented AddResponseDefinitionCommand --- .../datamodels/cmd/CommandFactory.java | 5 + .../AddResponseDefinitionCommand.java | 239 ++++++++++++++++++ ...sBodyAndFormDataMutualExclusivityRule.java | 16 +- .../apicurio/datamodels/util/CommandUtil.ts | 2 + src/test/resources/fixtures/cmd/tests.json | 6 +- 5 files changed, 259 insertions(+), 9 deletions(-) create mode 100755 src/main/java/io/apicurio/datamodels/cmd/commands/AddResponseDefinitionCommand.java diff --git a/src/main/java/io/apicurio/datamodels/cmd/CommandFactory.java b/src/main/java/io/apicurio/datamodels/cmd/CommandFactory.java index d6867f32..18bd8a2c 100644 --- a/src/main/java/io/apicurio/datamodels/cmd/CommandFactory.java +++ b/src/main/java/io/apicurio/datamodels/cmd/CommandFactory.java @@ -5,6 +5,7 @@ import io.apicurio.datamodels.cmd.commands.AddChannelItemCommand; import io.apicurio.datamodels.cmd.commands.AddExampleCommand; import io.apicurio.datamodels.cmd.commands.AddPathItemCommand; +import io.apicurio.datamodels.cmd.commands.AddResponseDefinitionCommand; import io.apicurio.datamodels.cmd.commands.ChangeContactCommand; import io.apicurio.datamodels.cmd.commands.ChangeDescriptionCommand; import io.apicurio.datamodels.cmd.commands.ChangeLicenseCommand; @@ -66,6 +67,10 @@ public static ICommand createAddHeaderExampleCommand(OpenApiMediaType mediaType, return new AddExampleCommand((OpenApiExamplesParent) mediaType, example, exampleName, exampleSummary, exampleDescription); } + public static ICommand createAddResponseDefinitionCommand(String definitionName, ObjectNode from) { + return new AddResponseDefinitionCommand(definitionName, from); + } + public static ICommand createChangePropertyCommand(Node node, String property, T newValue) { return new ChangePropertyCommand(node, property, newValue); } diff --git a/src/main/java/io/apicurio/datamodels/cmd/commands/AddResponseDefinitionCommand.java b/src/main/java/io/apicurio/datamodels/cmd/commands/AddResponseDefinitionCommand.java new file mode 100755 index 00000000..14cb52f3 --- /dev/null +++ b/src/main/java/io/apicurio/datamodels/cmd/commands/AddResponseDefinitionCommand.java @@ -0,0 +1,239 @@ +package io.apicurio.datamodels.cmd.commands; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.apicurio.datamodels.Library; +import io.apicurio.datamodels.cmd.AbstractCommand; +import io.apicurio.datamodels.models.Document; +import io.apicurio.datamodels.models.openapi.OpenApiDocument; +import io.apicurio.datamodels.models.openapi.OpenApiResponse; +import io.apicurio.datamodels.models.openapi.v20.OpenApi20Document; +import io.apicurio.datamodels.models.openapi.v20.OpenApi20Response; +import io.apicurio.datamodels.models.openapi.v30.OpenApi30Document; +import io.apicurio.datamodels.models.openapi.v30.OpenApi30Response; +import io.apicurio.datamodels.models.openapi.v31.OpenApi31Document; +import io.apicurio.datamodels.models.openapi.v31.OpenApi31Response; +import io.apicurio.datamodels.util.LoggerUtil; +import io.apicurio.datamodels.util.ModelTypeUtil; + +/** + * A command used to add a new response definition in a document. Source for the new + * definition must be provided. This source will be converted to an openapi + * response definition object and then added to the data model. + * @author eric.wittmann@gmail.com + */ +public class AddResponseDefinitionCommand extends AbstractCommand { + + public boolean _defExisted; + public String _newDefinitionName; + public ObjectNode _newDefinitionObj; + public boolean _nullDefinitionsParent; + + private transient AddResponseDefinitionCommandHelper _helper; + + public AddResponseDefinitionCommand() { + } + + public AddResponseDefinitionCommand(String definitionName, ObjectNode obj) { + this._newDefinitionName = definitionName; + this._newDefinitionObj = obj; + } + + /** + * @see io.apicurio.datamodels.cmd.ICommand#execute(Document) + */ + @Override + public void execute(Document document) { + LoggerUtil.info("[AddResponseDefinitionCommand] Executing."); + this._helper = createHelper(document); + + OpenApiDocument doc = (OpenApiDocument) document; + + // Do nothing if the definition already exists. + if (this._helper.defExists(doc)) { + LoggerUtil.info("[AddResponseDefinitionCommand] Definition with name %s already exists.", this._newDefinitionName); + this._defExisted = true; + return; + } + + this._nullDefinitionsParent = this._helper.prepareDocumentForDef(doc); + + OpenApiResponse definition = this._helper.createResponseDefinition(doc); + this._helper.addDefinition(doc, definition); + } + + /** + * @see io.apicurio.datamodels.cmd.ICommand#undo(Document) + */ + @Override + public void undo(Document document) { + LoggerUtil.info("[AddResponseDefinitionCommand] Reverting."); + if (this._defExisted) { + return; + } + + this._helper = createHelper(document); + + OpenApiDocument doc = (OpenApiDocument) document; + this._helper.removeDefinition(doc); + } + + private AddResponseDefinitionCommandHelper createHelper(Document document) { + if (ModelTypeUtil.isOpenApi2Model(document)) { + return new OpenApi20Helper(); + } + if (ModelTypeUtil.isOpenApi30Model(document)) { + return new OpenApi30Helper(); + } + if (ModelTypeUtil.isOpenApi31Model(document)) { + return new OpenApi31Helper(); + } + throw new RuntimeException("Unsupported model type: " + document.root().modelType()); + } + + private interface AddResponseDefinitionCommandHelper { + + boolean defExists(OpenApiDocument document); + boolean prepareDocumentForDef(OpenApiDocument document); + OpenApiResponse createResponseDefinition(OpenApiDocument document); + void addDefinition(OpenApiDocument document, OpenApiResponse definition); + void removeDefinition(OpenApiDocument document); + + } + + private class OpenApi20Helper implements AddResponseDefinitionCommandHelper { + @Override + public boolean defExists(OpenApiDocument document) { + OpenApi20Document doc = (OpenApi20Document) document; + if (!isNullOrUndefined(doc.getResponses())) { + return !isNullOrUndefined(doc.getResponses().getItem(_newDefinitionName)); + } + return false; + } + + @Override + public boolean prepareDocumentForDef(OpenApiDocument document) { + OpenApi20Document doc20 = (OpenApi20Document) document; + if (isNullOrUndefined(doc20.getResponses())) { + doc20.setResponses(doc20.createResponseDefinitions()); + return true; + } + return false; + } + + @Override + public OpenApiResponse createResponseDefinition(OpenApiDocument document) { + OpenApi20Document doc20 = (OpenApi20Document) document; + OpenApi20Response definition = doc20.getResponses().createResponse(); + Library.readNode(_newDefinitionObj, definition); + return definition; + } + + @Override + public void addDefinition(OpenApiDocument document, OpenApiResponse definition) { + OpenApi20Document doc20 = (OpenApi20Document) document; + OpenApi20Response def20 = (OpenApi20Response) definition; + doc20.getResponses().addItem(_newDefinitionName, def20); + } + + @Override + public void removeDefinition(OpenApiDocument document) { + OpenApi20Document doc20 = (OpenApi20Document) document; + if (_nullDefinitionsParent) { + doc20.setResponses(null); + } else { + doc20.getResponses().removeItem(_newDefinitionName); + } + } + } + + private class OpenApi30Helper implements AddResponseDefinitionCommandHelper { + @Override + public boolean defExists(OpenApiDocument document) { + OpenApi30Document doc30 = (OpenApi30Document) document; + if (isNullOrUndefined(doc30.getComponents())) { + return false; + } + return !isNullOrUndefined(doc30.getComponents().getResponses().get(_newDefinitionName)); + } + + @Override + public boolean prepareDocumentForDef(OpenApiDocument document) { + OpenApi30Document doc30 = (OpenApi30Document) document; + if (isNullOrUndefined(doc30.getComponents())) { + doc30.setComponents(doc30.createComponents()); + return true; + } + return false; + } + + @Override + public OpenApiResponse createResponseDefinition(OpenApiDocument document) { + OpenApi30Document doc30 = (OpenApi30Document) document; + OpenApi30Response definition = (OpenApi30Response) doc30.getComponents().createResponse(); + Library.readNode(_newDefinitionObj, definition); + return definition; + } + + @Override + public void addDefinition(OpenApiDocument document, OpenApiResponse definition) { + OpenApi30Document doc30 = (OpenApi30Document) document; + doc30.getComponents().addResponse(_newDefinitionName, definition); + } + + @Override + public void removeDefinition(OpenApiDocument document) { + OpenApi30Document doc30 = (OpenApi30Document) document; + if (_nullDefinitionsParent) { + doc30.setComponents(null); + } else { + doc30.getComponents().removeResponse(_newDefinitionName); + } + } + } + + private class OpenApi31Helper implements AddResponseDefinitionCommandHelper { + @Override + public boolean defExists(OpenApiDocument document) { + OpenApi31Document doc31 = (OpenApi31Document) document; + if (isNullOrUndefined(doc31.getComponents())) { + return false; + } + return !isNullOrUndefined(doc31.getComponents().getResponses().get(_newDefinitionName)); + } + + @Override + public boolean prepareDocumentForDef(OpenApiDocument document) { + OpenApi31Document doc31 = (OpenApi31Document) document; + if (isNullOrUndefined(doc31.getComponents())) { + doc31.setComponents(doc31.createComponents()); + return true; + } + return false; + } + + @Override + public OpenApiResponse createResponseDefinition(OpenApiDocument document) { + OpenApi31Document doc31 = (OpenApi31Document) document; + OpenApi31Response definition = (OpenApi31Response) doc31.getComponents().createResponse(); + Library.readNode(_newDefinitionObj, definition); + return definition; + } + + @Override + public void addDefinition(OpenApiDocument document, OpenApiResponse definition) { + OpenApi31Document doc31 = (OpenApi31Document) document; + doc31.getComponents().addResponse(_newDefinitionName, definition); + } + + @Override + public void removeDefinition(OpenApiDocument document) { + OpenApi31Document doc31 = (OpenApi31Document) document; + if (_nullDefinitionsParent) { + doc31.setComponents(null); + } else { + doc31.getComponents().removeResponse(_newDefinitionName); + } + } + } + +} diff --git a/src/main/java/io/apicurio/datamodels/validation/rules/mutex/OasBodyAndFormDataMutualExclusivityRule.java b/src/main/java/io/apicurio/datamodels/validation/rules/mutex/OasBodyAndFormDataMutualExclusivityRule.java index 56c2fe71..fd47528c 100755 --- a/src/main/java/io/apicurio/datamodels/validation/rules/mutex/OasBodyAndFormDataMutualExclusivityRule.java +++ b/src/main/java/io/apicurio/datamodels/validation/rules/mutex/OasBodyAndFormDataMutualExclusivityRule.java @@ -16,16 +16,16 @@ package io.apicurio.datamodels.validation.rules.mutex; -import java.util.List; - import io.apicurio.datamodels.models.Node; import io.apicurio.datamodels.models.Operation; import io.apicurio.datamodels.models.openapi.OpenApiParameter; +import io.apicurio.datamodels.models.openapi.OpenApiParametersParent; import io.apicurio.datamodels.models.openapi.OpenApiPathItem; -import io.apicurio.datamodels.util.NodeUtil; import io.apicurio.datamodels.validation.ValidationRule; import io.apicurio.datamodels.validation.ValidationRuleMetaData; +import java.util.List; + /** * Implements the Body and Form Data Mutual Exclusivity Rule. * @author eric.wittmann@gmail.com @@ -40,9 +40,9 @@ public OasBodyAndFormDataMutualExclusivityRule(ValidationRuleMetaData ruleInfo) super(ruleInfo); } - private void visitParameterParent(Node paramParent) { + private void visitParameterParent(OpenApiParametersParent paramParent) { @SuppressWarnings("unchecked") - List parameters = (List) NodeUtil.getNodeProperty(paramParent, "parameters"); + List parameters = paramParent.getParameters(); if (hasValue(parameters)) { boolean hasBodyParam = false; boolean hasFormDataParam = false; @@ -54,7 +54,7 @@ private void visitParameterParent(Node paramParent) { hasFormDataParam = true; } } - this.reportIf(hasBodyParam && hasFormDataParam, paramParent, "in", map()); + this.reportIf(hasBodyParam && hasFormDataParam, (Node) paramParent, "in", map()); } } @@ -63,7 +63,7 @@ private void visitParameterParent(Node paramParent) { */ @Override public void visitOperation(Operation node) { - visitParameterParent(node); + visitParameterParent((OpenApiParametersParent) node); } /** @@ -71,7 +71,7 @@ public void visitOperation(Operation node) { */ @Override public void visitPathItem(OpenApiPathItem node) { - visitParameterParent(node); + visitParameterParent((OpenApiParametersParent) node); } } diff --git a/src/main/ts/src/io/apicurio/datamodels/util/CommandUtil.ts b/src/main/ts/src/io/apicurio/datamodels/util/CommandUtil.ts index c73a4cb3..43040d15 100644 --- a/src/main/ts/src/io/apicurio/datamodels/util/CommandUtil.ts +++ b/src/main/ts/src/io/apicurio/datamodels/util/CommandUtil.ts @@ -5,6 +5,7 @@ import {NodePathUtil} from "../paths/NodePathUtil"; import {AddChannelItemCommand} from "../cmd/commands/AddChannelItemCommand"; import {AddExampleCommand} from "../cmd/commands/AddExampleCommand"; import {AddPathItemCommand} from "../cmd/commands/AddPathItemCommand"; +import {AddResponseDefinitionCommand} from "../cmd/commands/AddResponseDefinitionCommand"; import {ChangeDescriptionCommand} from "../cmd/commands/ChangeDescriptionCommand"; import {ChangePropertyCommand} from "../cmd/commands/ChangePropertyCommand"; @@ -46,6 +47,7 @@ const commandSuppliers: { [key: string]: Supplier } = { "AddChannelItemCommand": () => { return new AddChannelItemCommand(); }, "AddExampleCommand": () => { return new AddExampleCommand(); }, "AddPathItemCommand": () => { return new AddPathItemCommand(); }, + "AddResponseDefinitionCommand": () => { return new AddResponseDefinitionCommand(); }, "ChangeDescriptionCommand": () => { return new ChangeDescriptionCommand(); }, "ChangePropertyCommand": () => { return new ChangePropertyCommand(); }, diff --git a/src/test/resources/fixtures/cmd/tests.json b/src/test/resources/fixtures/cmd/tests.json index a37072fd..86dfcaba 100755 --- a/src/test/resources/fixtures/cmd/tests.json +++ b/src/test/resources/fixtures/cmd/tests.json @@ -67,5 +67,9 @@ { "name": "[OpenAPI 2] {Add Path Item} - Add Path Item", "test": "commands/add-path-item/openapi-2/add-path-item" }, { "name": "[OpenAPI 2] {Add Path Item} - Clone Path Item", "test": "commands/add-path-item/openapi-2/clone-path-item" }, { "name": "[OpenAPI 3] {Add Path Item} - Add Path Item", "test": "commands/add-path-item/openapi-3/add-path-item" }, - { "name": "[OpenAPI 3] {Add Path Item} - Clone Path Item", "test": "commands/add-path-item/openapi-3/clone-path-item" } + { "name": "[OpenAPI 3] {Add Path Item} - Clone Path Item", "test": "commands/add-path-item/openapi-3/clone-path-item" }, + { "name": "[OpenAPI 2] {Add Response Definition} - Add Response Definition", "test": "commands/add-response-definition/openapi-2/add-response-definition" }, + { "name": "[OpenAPI 2] {Add Response Definition} - Clone Response Definition", "test": "commands/add-response-definition/openapi-2/clone-response-definition" }, + { "name": "[OpenAPI 3] {Add Response Definition} - Add Response Definition", "test": "commands/add-response-definition/openapi-3/add-response-definition" }, + { "name": "[OpenAPI 3] {Add Response Definition} - Clone Response Definition", "test": "commands/add-response-definition/openapi-3/clone-response-definition" } ] \ No newline at end of file