From bc982b91025a6962ba17a7af9cb1efc990263382 Mon Sep 17 00:00:00 2001 From: Michele Santoro <10807610+michelu89@users.noreply.github.com> Date: Wed, 9 Nov 2022 14:46:12 +0100 Subject: [PATCH] Feature/generate open api specification (#8) --- .graalvm/reflect-config.json | 23 +- .graalvm/resource-config.json | 18 ++ postman/ame.postman_collection.json | 282 +++++++++++++++++- .../exceptions/ResponseExceptionHandler.java | 29 +- .../ame/services/GenerateService.java | 67 ++++- .../ame/services/ModelService.java | 2 +- .../ame/services/utils/ModelUtils.java | 102 +++++-- .../ame/web/GenerateResource.java | 44 ++- .../ame/services/GenerateServiceTest.java | 45 ++- 9 files changed, 531 insertions(+), 81 deletions(-) diff --git a/.graalvm/reflect-config.json b/.graalvm/reflect-config.json index d009ee33..3bc353e6 100644 --- a/.graalvm/reflect-config.json +++ b/.graalvm/reflect-config.json @@ -334,11 +334,22 @@ ] }, { - "name" : "java.lang.Class", - "methods" : [ - { "name" : "getSimpleName", "parameterTypes" : [] }, - { "name" : "getInterfaces", "parameterTypes" : [] }, - { "name" : "getInterfaces", "parameterTypes" : ["java.lang.boolean"] } + "name": "java.lang.Class", + "methods": [ + { + "name": "getSimpleName", + "parameterTypes": [] + }, + { + "name": "getInterfaces", + "parameterTypes": [] + }, + { + "name": "getInterfaces", + "parameterTypes": [ + "java.lang.boolean" + ] + } ] }, { @@ -1575,4 +1586,4 @@ "allDeclaredClasses": true, "allPublicClasses": true } -] \ No newline at end of file +] diff --git a/.graalvm/resource-config.json b/.graalvm/resource-config.json index a76be04d..5aae2f13 100644 --- a/.graalvm/resource-config.json +++ b/.graalvm/resource-config.json @@ -71,6 +71,24 @@ }, { "pattern": "\\Qorg/apache/batik/transcoder/Transcoder.class\\E" + }, + { + "pattern": "\\Qopenapi/OpenApiRootJson.json\\E" + }, + { + "pattern": "\\Qopenapi/CursorBasedPaging.json\\E" + }, + { + "pattern": "\\Qopenapi/Filter.json\\E" + }, + { + "pattern": "\\Qopenapi/JsonRPC.json\\E" + }, + { + "pattern": "\\Qopenapi/OffsetBasedPaging.json\\E" + }, + { + "pattern": "\\Qopenapi/TimeBasedPaging.json\\E" } ] } diff --git a/postman/ame.postman_collection.json b/postman/ame.postman_collection.json index 24757131..7e931c8c 100644 --- a/postman/ame.postman_collection.json +++ b/postman/ame.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "60fc3835-36b0-43ff-a9b4-084c8616844b", + "_postman_id": "d11906f6-ce45-4709-94ff-d7bbab64eef5", "name": "AME.POSTMAN.RESOURCES", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -25,7 +25,7 @@ "header": [], "body": { "mode": "raw", - "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:name \"AspectDefault\" ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:name \"property1\";\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:name \"Characteristic1\" ;\r\n bamm:dataType xsd:string .\r\n" + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:dataType xsd:string .\r\n" }, "url": { "raw": "http://localhost:{{port}}/ame/api/models", @@ -70,7 +70,7 @@ ], "body": { "mode": "raw", - "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:name \"AspectDefault\" ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:name \"property1\";\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:name \"Characteristic1\" ;\r\n bamm:dataType xsd:string .\r\n" + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:dataType xsd:string .\r\n" }, "url": { "raw": "http://localhost:{{port}}/ame/api/models", @@ -273,7 +273,7 @@ "header": [], "body": { "mode": "raw", - "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:name \"AspectDefault\" ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:name \"property1\";\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:name \"Characteristic1\" ;\r\n bamm:dataType xsd:string .\r\n" + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:dataType xsd:string .\r\n" }, "url": { "raw": "http://localhost:{{port}}/ame/api/models/validate", @@ -314,7 +314,7 @@ "header": [], "body": { "mode": "raw", - "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:DefaultAspect a bamm:Aspect ;\r\n bamm:name \"DefaultAspect\" ;\r\n bamm:preferredName \"DefaultAspect\"@en ;\r\n bamm:description \"DefaultAspect Description\"@en ;\r\n bamm:properties ( :property1 ) ;\r\n bamm:operations ( ) .\r\n\r\n:property1 a bamm:Property ;\r\n bamm:name \"property1\" ;\r\n bamm:preferredName \"Property 1 \"@en ;\r\n bamm:description \"Property Description 1 \"@en ;\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:name \"Characteristic1\" ;\r\n bamm:preferredName \"Characteristic 1 \"@en ;\r\n bamm:description \"Characteristic Description 1 \"@en ;\r\n bamm:dataType xsd:string .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:name \"Characteristic2\" ;\r\n bamm:preferredName \"Characteristic 2 \"@en ;\r\n bamm:description \"Characteristic Description 2 \"@en ;\r\n bamm:dataType xsd:string ." + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:DefaultAspect a bamm:Aspect ;\r\n bamm:preferredName \"DefaultAspect\"@en ;\r\n bamm:description \"DefaultAspect Description\"@en ;\r\n bamm:properties ( :property1 ) ;\r\n bamm:operations ( ) .\r\n\r\n:property1 a bamm:Property ;\r\n bamm:preferredName \"Property 1 \"@en ;\r\n bamm:description \"Property Description 1 \"@en ;\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:preferredName \"Characteristic 1 \"@en ;\r\n bamm:description \"Characteristic Description 1 \"@en ;\r\n bamm:dataType xsd:string .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:preferredName \"Characteristic 2 \"@en ;\r\n bamm:description \"Characteristic Description 2 \"@en ;\r\n bamm:dataType xsd:string ." }, "url": { "raw": "http://localhost:{{port}}/ame/api/models/validate", @@ -355,7 +355,7 @@ "header": [], "body": { "mode": "raw", - "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:name \"AspectDefaultUpdate\" ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:name \"property1Update\";\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:name \"Characteristic1Update\" \r\n bamm:dataType xsd:string .\r\n" + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:characteristic :Characteristic1\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:dataType xsd:string .\r\n" }, "url": { "raw": "http://localhost:{{port}}/ame/api/models/validate", @@ -437,7 +437,7 @@ "header": [], "body": { "mode": "raw", - "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectWithUsedAndUnusedConstraint a bamm:Aspect ;\r\n bamm:name \"AspectWithUsedAndUnusedConstraint\" ;\r\n bamm:properties ( :testProperty ) ;\r\n bamm:operations ( ) .\r\n\r\n:testProperty a bamm:Property ;\r\n bamm:name \"testProperty\" ;\r\n bamm:characteristic :UsedTestTrait .\r\n\r\n:UsedTestTrait a bamm-c:Trait ;\r\n bamm:name \"UsedTestTrait\" ;\r\n bamm-c:constraint :UnusedTestConstraint ;\r\n bamm-c:baseCharacteristic bamm-c:Text .\r\n\r\n:UnusedTestConstraint a bamm-c:LengthConstraint ;\r\n bamm:name \"UnusedTestConstraint\" ;\r\n bamm:preferredName \"Unused Test Constraint\"@en ;\r\n bamm:description \"Unused Test Constraint\"@en ;\r\n bamm:see ;\r\n bamm:see ;\r\n bamm-c:minValue \"5\"^^xsd:nonNegativeInteger ;\r\n bamm-c:maxValue \"10\"^^xsd:nonNegativeInteger ." + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectWithUsedAndUnusedConstraint a bamm:Aspect ;\r\n bamm:properties ( :testProperty ) ;\r\n bamm:operations ( ) .\r\n\r\n:testProperty a bamm:Property ;\r\n bamm:characteristic :UsedTestTrait .\r\n\r\n:UsedTestTrait a bamm-c:Trait ;\r\n bamm-c:constraint :UnusedTestConstraint ;\r\n bamm-c:baseCharacteristic bamm-c:Text .\r\n\r\n:UnusedTestConstraint a bamm-c:LengthConstraint ;\r\n bamm:preferredName \"Unused Test Constraint\"@en ;\r\n bamm:description \"Unused Test Constraint\"@en ;\r\n bamm:see ;\r\n bamm:see ;\r\n bamm-c:minValue \"5\"^^xsd:nonNegativeInteger ;\r\n bamm-c:maxValue \"10\"^^xsd:nonNegativeInteger ." }, "url": { "raw": "http://localhost:{{port}}/ame/api/models/validate", @@ -479,7 +479,7 @@ "header": [], "body": { "mode": "raw", - "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:name \"AspectDefault\" ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:name \"property1\";\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:name \"Characteristic1\" ;\r\n bamm:dataType xsd:string .\r\n" + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:dataType xsd:string .\r\n" }, "url": { "raw": "http://localhost:{{port}}/ame/api/generate/json-sample", @@ -522,7 +522,7 @@ "header": [], "body": { "mode": "raw", - "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:name \"AspectDefault\" ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:name \"property1\";\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:name \"Characteristic1\" ;\r\n bamm:dataType xsd:string .\r\n" + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:dataType xsd:string .\r\n" }, "url": { "raw": "http://localhost:{{port}}/ame/api/generate/json-schema", @@ -561,7 +561,7 @@ "header": [], "body": { "mode": "raw", - "raw": "@prefix bamm: .\n@prefix bamm-c: .\n@prefix bamm-e: .\n@prefix unit: .\n@prefix rdf: .\n@prefix rdfs: .\n@prefix xsd: .\n@prefix : .\n\n:AspectDefault a bamm:Aspect ;\n bamm:name \"AspectDefault\" ;\n bamm:properties (:property1) ;\n bamm:operations () .\n\n:property1 a bamm:Property;\n bamm:name \"property1\";\n bamm:characteristic :Characteristic1 .\n\n:Characteristic1 a bamm:Characteristic ;\n bamm:name \"Characteristic1\" ." + "raw": "@prefix bamm: .\n@prefix bamm-c: .\n@prefix bamm-e: .\n@prefix unit: .\n@prefix rdf: .\n@prefix rdfs: .\n@prefix xsd: .\n@prefix : .\n\n:AspectDefault a bamm:Aspect ;\n bamm:properties (:property1) ;\n bamm:operations () .\n\n:property1 a bamm:Property;\n bamm:characteristic :Characteristic1 .\n\n:Characteristic1 a bamm:Characteristic ." }, "url": { "raw": "http://localhost:{{port}}/ame/api/generate/json-schema", @@ -600,7 +600,7 @@ "header": [], "body": { "mode": "raw", - "raw": "@prefix bamm: .\n@prefix bamm-c: .\n@prefix bamm-e: .\n@prefix unit: .\n@prefix rdf: .\n@prefix rdfs: .\n@prefix xsd: .\n@prefix : .\n\n:AspectDefault a bamm:Aspect ;\n bamm:name \"AspectDefault\" ;\n bamm:properties (:property1) ;\n bamm:operations () .\n\n:property1 a bamm:Property;\n bamm:name \"property1\";\n bamm:characteristic :Characteristic1 .\n\n:Characteristic1 a bamm:Characteristic ;\n bamm:name \"Characteristic1\" ." + "raw": "@prefix bamm: .\n@prefix bamm-c: .\n@prefix bamm-e: .\n@prefix unit: .\n@prefix rdf: .\n@prefix rdfs: .\n@prefix xsd: .\n@prefix : .\n\n:AspectDefault a bamm:Aspect ;\n bamm:properties (:property1) ;\n bamm:operations () .\n\n:property1 a bamm:Property;\n bamm:characteristic :Characteristic1 .\n\n:Characteristic1 a bamm:Characteristic ." }, "url": { "raw": "http://localhost:{{port}}/ame/api/generate/json-sample", @@ -618,6 +618,266 @@ } }, "response": [] + }, + { + "name": "GenerateJsonOpenApiSpec", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "pm.test(\"Response body is valid\", function () {\r", + " const jsonData = pm.response.json();\r", + " pm.expect(jsonData.openapi).to.equal(\"3.0.3\");\r", + " pm.expect(jsonData.info.title).to.equal(\"AspectDefault\");\r", + " pm.expect(jsonData.info.version).to.equal(\"v1.0.0\");\r", + " pm.expect(jsonData.servers[0].url).to.equal(\"http://www.test.com/api/v1.0.0\");\r", + "\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:dataType xsd:string .\r\n" + }, + "url": { + "raw": "http://localhost:{{port}}/ame/api/generate/open-api-spec?output=json&baseUrl=http://www.test.com&includeQueryApi=true&useSemanticVersion=true&pagingOption=NO_PAGING", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "{{port}}", + "path": [ + "ame", + "api", + "generate", + "open-api-spec" + ], + "query": [ + { + "key": "output", + "value": "json" + }, + { + "key": "baseUrl", + "value": "http://www.test.com" + }, + { + "key": "includeQueryApi", + "value": "true" + }, + { + "key": "useSemanticVersion", + "value": "true" + }, + { + "key": "pagingOption", + "value": "NO_PAGING" + } + ] + } + }, + "response": [] + }, + { + "name": "GenerateYamlOpenApiSpec", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "pm.test(\"Response body is valid\", function () {\r", + " const jsonData = pm.response.text();\r", + " pm.expect(jsonData).to.include(\"openapi: 3.0.3\");\r", + " pm.expect(jsonData).to.include(\"title: AspectDefault\");\r", + " pm.expect(jsonData).to.include(\"version: v1\");\r", + " pm.expect(jsonData).to.include(\"url: http://www.test.com/api/v1\");\r", + "\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic ;\r\n bamm:dataType xsd:string .\r\n" + }, + "url": { + "raw": "http://localhost:{{port}}/ame/api/generate/open-api-spec?output=yaml&baseUrl=http://www.test.com&includeQueryApi=false&useSemanticVersion=false&pagingOption=OFFSET_BASED_PAGING", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "{{port}}", + "path": [ + "ame", + "api", + "generate", + "open-api-spec" + ], + "query": [ + { + "key": "output", + "value": "yaml" + }, + { + "key": "baseUrl", + "value": "http://www.test.com" + }, + { + "key": "includeQueryApi", + "value": "false" + }, + { + "key": "useSemanticVersion", + "value": "false" + }, + { + "key": "pagingOption", + "value": "OFFSET_BASED_PAGING" + } + ] + } + }, + "response": [] + }, + { + "name": "GenerateJsonOpenApiSpecFromInvalidAspectModel", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(422);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic .\r\n" + }, + "url": { + "raw": "http://localhost:{{port}}/ame/api/generate/open-api-spec?output=json&baseUrl=http://www.test.com&includeQueryApi=true&useSemanticVersion=true&pagingOption=NO_PAGING", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "{{port}}", + "path": [ + "ame", + "api", + "generate", + "open-api-spec" + ], + "query": [ + { + "key": "output", + "value": "json" + }, + { + "key": "baseUrl", + "value": "http://www.test.com" + }, + { + "key": "includeQueryApi", + "value": "true" + }, + { + "key": "useSemanticVersion", + "value": "true" + }, + { + "key": "pagingOption", + "value": "NO_PAGING" + } + ] + } + }, + "response": [] + }, + { + "name": "GenerateYamlOpenApiSpecFromInvalidAspectModel", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(422);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "@prefix bamm: .\r\n@prefix bamm-c: .\r\n@prefix bamm-e: .\r\n@prefix unit: .\r\n@prefix rdf: .\r\n@prefix rdfs: .\r\n@prefix xsd: .\r\n@prefix : .\r\n\r\n:AspectDefault a bamm:Aspect ;\r\n bamm:properties (:property1) ;\r\n bamm:operations () .\r\n\r\n:property1 a bamm:Property;\r\n bamm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a bamm:Characteristic .\r\n" + }, + "url": { + "raw": "http://localhost:{{port}}/ame/api/generate/open-api-spec?output=yaml&baseUrl=http://www.test.com&includeQueryApi=true&useSemanticVersion=true&pagingOption=NO_PAGING", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "{{port}}", + "path": [ + "ame", + "api", + "generate", + "open-api-spec" + ], + "query": [ + { + "key": "output", + "value": "yaml" + }, + { + "key": "baseUrl", + "value": "http://www.test.com" + }, + { + "key": "includeQueryApi", + "value": "true" + }, + { + "key": "useSemanticVersion", + "value": "true" + }, + { + "key": "pagingOption", + "value": "NO_PAGING" + } + ] + } + }, + "response": [] } ], "event": [ diff --git a/src/main/java/io/openmanufacturing/ame/exceptions/ResponseExceptionHandler.java b/src/main/java/io/openmanufacturing/ame/exceptions/ResponseExceptionHandler.java index 9d711ab5..e7347771 100644 --- a/src/main/java/io/openmanufacturing/ame/exceptions/ResponseExceptionHandler.java +++ b/src/main/java/io/openmanufacturing/ame/exceptions/ResponseExceptionHandler.java @@ -30,6 +30,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import io.openmanufacturing.ame.exceptions.model.ErrorResponse; +import io.openmanufacturing.sds.metamodel.loader.AspectLoadingException; /** * Provides custom exception handling for the REST API. @@ -103,22 +104,35 @@ public ResponseEntity handleInvalidStateTransitionException( final WebRe return error( HttpStatus.BAD_REQUEST, request, e, e.getMessage() ); } + /** + * Method for handling exception of type {@link AspectLoadingException} + * + * @param request the Http request + * @param e the exception which occurred + * @return the custom {@link ErrorResponse} as {@link ResponseEntity} for the exception + */ + @ExceptionHandler( AspectLoadingException.class ) + public ResponseEntity handleAspectLoadingException( final WebRequest request, + final AspectLoadingException e ) { + return error( HttpStatus.UNPROCESSABLE_ENTITY, request, e, e.getMessage() ); + } + /** * Method for handling exception of type {@link InvalidAspectModelException} * * @param request the Http request - * @param e the exception which occurred + * @param e the exception which occurred * @return the custom {@link ErrorResponse} as {@link ResponseEntity} for the exception */ - @ExceptionHandler(InvalidAspectModelException.class) - public ResponseEntity handleInvalidAspectModelException(final WebRequest request, - final InvalidAspectModelException e) { - return error(HttpStatus.BAD_REQUEST, request, e, e.getMessage()); + @ExceptionHandler( InvalidAspectModelException.class ) + public ResponseEntity handleInvalidAspectModelException( final WebRequest request, + final InvalidAspectModelException e ) { + return error( HttpStatus.BAD_REQUEST, request, e, e.getMessage() ); } private ResponseEntity error( final HttpStatus responseCode, final WebRequest request, final RuntimeException e, final String message ) { - logRequest(request, e, responseCode); + logRequest( request, e, responseCode ); final ErrorResponse errorResponse = new ErrorResponse( message, ((ServletWebRequest) request).getRequest().getRequestURI(), responseCode.value() ); @@ -138,7 +152,8 @@ protected static void logRequest( final WebRequest request, final Throwable ex, } } - private static String getLogRequestMessage( final WebRequest request, final Throwable ex, final HttpStatus httpStatus ) { + private static String getLogRequestMessage( final WebRequest request, final Throwable ex, + final HttpStatus httpStatus ) { final HttpServletRequest servletRequest = ((ServletWebRequest) request).getRequest(); return servletRequest.getQueryString() == null ? getLogRequestMessage( servletRequest.getRequestURI(), ex, httpStatus ) : diff --git a/src/main/java/io/openmanufacturing/ame/services/GenerateService.java b/src/main/java/io/openmanufacturing/ame/services/GenerateService.java index c5d4f1b9..a1e63779 100644 --- a/src/main/java/io/openmanufacturing/ame/services/GenerateService.java +++ b/src/main/java/io/openmanufacturing/ame/services/GenerateService.java @@ -31,6 +31,8 @@ import io.openmanufacturing.sds.aspectmodel.generator.docu.AspectModelDocumentationGenerator; import io.openmanufacturing.sds.aspectmodel.generator.json.AspectModelJsonPayloadGenerator; import io.openmanufacturing.sds.aspectmodel.generator.jsonschema.AspectModelJsonSchemaGenerator; +import io.openmanufacturing.sds.aspectmodel.generator.openapi.AspectModelOpenApiGenerator; +import io.openmanufacturing.sds.aspectmodel.generator.openapi.PagingOption; import io.openmanufacturing.sds.aspectmodel.resolver.services.DataType; import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel; import io.openmanufacturing.sds.metamodel.Aspect; @@ -41,15 +43,15 @@ @Service public class GenerateService { private static final Logger LOG = LoggerFactory.getLogger( GenerateService.class ); - + private static final String COULD_NOT_LOAD_ASPECT = "Could not load Aspect"; private static final String COULD_NOT_LOAD_ASPECT_MODEL = "Could not load Aspect model, please make sure the model is valid."; public GenerateService() { DataType.setupTypeMapping(); } - public byte[] generateHtmlDocument( final String aspectModel, final Optional storagePath ) - throws IOException { + public byte[] generateHtmlDocument( final String aspectModel ) throws IOException { + final Optional storagePath = Optional.of( ApplicationSettings.getMetaModelStoragePath() ); final AspectModelDocumentationGenerator generator = new AspectModelDocumentationGenerator( getVersionModel( aspectModel, storagePath ).get() ); final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); @@ -60,15 +62,15 @@ public byte[] generateHtmlDocument( final String aspectModel, final Optional storagePath ) { + public String jsonSchema( final String aspectModel ) { try { - final Aspect aspect = AspectModelLoader - .fromVersionedModel( getVersionModel( aspectModel, storagePath ).get() ) - .getOrElseThrow( e -> { - LOG.error( COULD_NOT_LOAD_ASPECT_MODEL ); - return new InvalidAspectModelException( - COULD_NOT_LOAD_ASPECT_MODEL, e ); - } ); + final Optional storagePath = Optional.of( ApplicationSettings.getMetaModelStoragePath() ); + final Aspect aspect = AspectModelLoader.fromVersionedModel( getVersionModel( aspectModel, storagePath ).get() ) + .getOrElseThrow( e -> { + LOG.error( COULD_NOT_LOAD_ASPECT_MODEL ); + return new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT_MODEL, + e ); + } ); final AspectModelJsonSchemaGenerator generator = new AspectModelJsonSchemaGenerator(); final JsonNode jsonSchema = generator.apply( aspect ); @@ -81,23 +83,23 @@ public String jsonSchema( final String aspectModel, final Optional stora return out.toString(); } catch ( final IOException e ) { LOG.error( "Aspect Model could not be loaded correctly." ); - throw new InvalidAspectModelException( "Could not load Aspect", e ); + throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT, e ); } } - public String sampleJSONPayload( final String aspectModel, final Optional storagePath ) { + public String sampleJSONPayload( final String aspectModel ) { try { + final Optional storagePath = Optional.of( ApplicationSettings.getMetaModelStoragePath() ); final AspectModelJsonPayloadGenerator generator = new AspectModelJsonPayloadGenerator( getVersionModel( aspectModel, storagePath ).get() ); return generator.generateJson(); } catch ( final AspectLoadingException e ) { LOG.error( COULD_NOT_LOAD_ASPECT_MODEL ); - throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT_MODEL, - e ); + throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT_MODEL, e ); } catch ( final IOException e ) { LOG.error( "Aspect Model could not be loaded correctly." ); - throw new InvalidAspectModelException( "Could not load Aspect", e ); + throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT, e ); } } @@ -105,4 +107,37 @@ private Try getVersionModel( final String aspectModel, final Opt return ModelUtils.fetchVersionModel( aspectModel, storagePath.orElse( ApplicationSettings.getMetaModelStoragePath() ) ); } + + public String generateYamlOpenApiSpec( final String aspectModel, final String baseUrl, final boolean includeQueryApi, + final boolean useSemanticVersion, final Optional pagingOption ) { + try { + final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator(); + + return generator.applyForYaml( ModelUtils.resolveAspectFromModel( aspectModel ), useSemanticVersion, baseUrl, + Optional.empty(), Optional.empty(), includeQueryApi, pagingOption ); + } catch ( final IOException e ) { + LOG.error( "YAML OpenAPI specification could not be generated." ); + throw new InvalidAspectModelException( "Error generating YAML OpenAPI specification", e ); + } + } + + public String generateJsonOpenApiSpec( final String aspectModel, final String baseUrl, + final boolean includeQueryApi, final boolean useSemanticVersion, final Optional pagingOption ) { + try { + final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator(); + + final JsonNode json = generator.applyForJson( ModelUtils.resolveAspectFromModel( aspectModel ), + useSemanticVersion, baseUrl, Optional.empty(), Optional.empty(), includeQueryApi, pagingOption ); + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final ObjectMapper objectMapper = new ObjectMapper(); + + objectMapper.writerWithDefaultPrettyPrinter().writeValue( out, json ); + + return out.toString(); + } catch ( final IOException e ) { + LOG.error( "JSON OpenAPI specification could not be generated." ); + throw new InvalidAspectModelException( "Error generating JSON OpenAPI specification", e ); + } + } } diff --git a/src/main/java/io/openmanufacturing/ame/services/ModelService.java b/src/main/java/io/openmanufacturing/ame/services/ModelService.java index 72cae321..aeb9ed1b 100644 --- a/src/main/java/io/openmanufacturing/ame/services/ModelService.java +++ b/src/main/java/io/openmanufacturing/ame/services/ModelService.java @@ -114,7 +114,7 @@ public Namespaces migrateWorkspace( final String storagePath ) { } private Try updateModelVersion( final File inputFile ) { - return ModelUtils.loadButNotResolveModel( inputFile ).flatMap( new MigratorService()::updateMetaModelVersion ); + return ModelUtils.loadModelFromFile( inputFile ).flatMap( new MigratorService()::updateMetaModelVersion ); } private Namespace resolveNamespace( final List namespaces, final AspectModelUrn aspectModelUrn ) { diff --git a/src/main/java/io/openmanufacturing/ame/services/utils/ModelUtils.java b/src/main/java/io/openmanufacturing/ame/services/utils/ModelUtils.java index 9bac75d6..1f29d512 100644 --- a/src/main/java/io/openmanufacturing/ame/services/utils/ModelUtils.java +++ b/src/main/java/io/openmanufacturing/ame/services/utils/ModelUtils.java @@ -24,8 +24,10 @@ import java.util.List; import java.util.regex.Pattern; +import org.apache.jena.rdf.model.Model; import org.apache.jena.riot.RiotException; +import io.openmanufacturing.ame.config.ApplicationSettings; import io.openmanufacturing.ame.exceptions.InvalidAspectModelException; import io.openmanufacturing.ame.exceptions.UrnNotFoundException; import io.openmanufacturing.ame.resolver.inmemory.InMemoryStrategy; @@ -40,19 +42,19 @@ import io.openmanufacturing.sds.aspectmodel.validation.report.ValidationReportBuilder; import io.openmanufacturing.sds.aspectmodel.validation.services.AspectModelValidator; import io.openmanufacturing.sds.aspectmodel.versionupdate.MigratorService; +import io.openmanufacturing.sds.metamodel.Aspect; +import io.openmanufacturing.sds.metamodel.loader.AspectModelLoader; import io.vavr.control.Try; +import lombok.NoArgsConstructor; +@NoArgsConstructor public class ModelUtils { public static final String TTL = "ttl"; public static final String TTL_EXTENSION = "." + TTL; public final static Pattern URN_PATTERN = Pattern.compile( - "^urn:[a-z0-9][a-z0-9-]{0,31}:([a-z0-9()+,\\-.:=@;$_!*#']|%[0-9a-f]{2})+$", - Pattern.CASE_INSENSITIVE ); - - private ModelUtils() { - } + "^urn:[a-z0-9][a-z0-9-]{0,31}:([a-z0-9()+,\\-.:=@;$_!*#']|%[0-9a-f]{2})+$", Pattern.CASE_INSENSITIVE ); /** * This Method is used to create an in memory strategy for the given Aspect Model. @@ -92,34 +94,70 @@ public static Try fetchVersionModel( final String aspectModel, f return new AspectModelResolver().resolveAspectModel( inMemoryStrategy, inMemoryStrategy.getAspectModelUrn() ); } - public static Try loadButNotResolveModel( final File inputFile ) { - try ( final InputStream inputStream = new FileInputStream( inputFile ) ) { - final SdsAspectMetaModelResourceResolver metaModelResourceResolver = new SdsAspectMetaModelResourceResolver(); - return TurtleLoader.loadTurtle( inputStream ).flatMap( model -> - metaModelResourceResolver.getBammVersion( model ).flatMap( metaModelVersion -> - metaModelResourceResolver.mergeMetaModelIntoRawModel( model, metaModelVersion ) ) ); + /** + * Migrates a model to its latest version. + * + * @param aspectModel as a string. + * @param storagePath stored path to the Aspect Models. + * @return migrated Aspect Model as a string. + */ + public static String migrateModel( final String aspectModel, final String storagePath ) { + final InMemoryStrategy inMemoryStrategy = ModelUtils.inMemoryStrategy( aspectModel, storagePath ); + + final Try migratedFile = LoadModelFromStoragePath( inMemoryStrategy ).flatMap( + new MigratorService()::updateMetaModelVersion ); + + final VersionedModel versionedModel = migratedFile.getOrElseThrow( + e -> new InvalidAspectModelException( "Aspect Model cannot be migrated.", e ) ); + + return getPrettyPrintedVersionedModel( versionedModel, inMemoryStrategy.getAspectModelUrn().getUrn() ); + } + + /** + * Creates an Aspect instance from an Aspect Model. + * + * @param aspectModel as a string. + * @return the Aspect as an object. + */ + public static Aspect resolveAspectFromModel( final String aspectModel ) { + final InMemoryStrategy inMemoryStrategy = ModelUtils.inMemoryStrategy( aspectModel, + ApplicationSettings.getMetaModelStoragePath() ); + + final VersionedModel versionedModel = ModelUtils.LoadModelFromStoragePath( inMemoryStrategy ).getOrElseThrow( + e -> new InvalidAspectModelException( "Cannot resolve Aspect Model.", e ) ); + + return AspectModelLoader.fromVersionedModelUnchecked( versionedModel ); + } + + /** + * Load Aspect Model from storage path. + * + * @param inMemoryStrategy for the given storage path. + * @return the resulting {@link VersionedModel} that corresponds to the input Aspect model. + */ + public static Try LoadModelFromStoragePath( final InMemoryStrategy inMemoryStrategy ) { + return resolveModel( inMemoryStrategy.model ); + } + + /** + * Loading the Aspect Model from input file. + * + * @param file Aspect Model as a file. + * @return the resulting {@link VersionedModel} that corresponds to the input Aspect model. + */ + public static Try loadModelFromFile( final File file ) { + try ( final InputStream inputStream = new FileInputStream( file ) ) { + return TurtleLoader.loadTurtle( inputStream ).flatMap( ModelUtils::resolveModel ); } catch ( final IOException exception ) { return Try.failure( exception ); } } - public static String migrateModel( final String aspectModel, final String storagePath ) { - final InMemoryStrategy inMemoryStrategy = inMemoryStrategy( aspectModel, storagePath ); - + private static Try resolveModel( final Model model ) { final SdsAspectMetaModelResourceResolver metaModelResourceResolver = new SdsAspectMetaModelResourceResolver(); - final Try migratedFile = metaModelResourceResolver.getBammVersion( inMemoryStrategy.model ) - .flatMap( metaModelVersion -> - metaModelResourceResolver.mergeMetaModelIntoRawModel( - inMemoryStrategy.model, - metaModelVersion ) ) - .flatMap( - new MigratorService()::updateMetaModelVersion ); - - final VersionedModel versionedModel = migratedFile.getOrElseThrow( - e -> new InvalidAspectModelException( "AspectModel cannot be migrated.", e ) ); - - return getPrettyPrintedVersionedModel( versionedModel, inMemoryStrategy.getAspectModelUrn().getUrn() ); + return metaModelResourceResolver.getBammVersion( model ).flatMap( + metaModelVersion -> metaModelResourceResolver.mergeMetaModelIntoRawModel( model, metaModelVersion ) ); } /** @@ -163,16 +201,14 @@ public static ValidationReport validateModel( final String aspectModel, final St } private static ValidationReport buildValidationSyntacticReport( final RiotException riotException ) { - return new ValidationReportBuilder() - .withValidationErrors( List.of( new ValidationError.Syntactic( riotException ) ) ) - .buildInvalidReport(); + return new ValidationReportBuilder().withValidationErrors( + List.of( new ValidationError.Syntactic( riotException ) ) ).buildInvalidReport(); } private static ValidationReport buildValidationSemanticReport( final String message, final String focusNode, final String resultPath, final String Severity, final String value ) { - return new ValidationReportBuilder() - .withValidationErrors( List - .of( new ValidationError.Semantic( message, focusNode, resultPath, Severity, value ) ) ) - .buildInvalidReport(); + final ValidationError.Semantic semantic = new ValidationError.Semantic( message, focusNode, resultPath, Severity, + value ); + return new ValidationReportBuilder().withValidationErrors( List.of( semantic ) ).buildInvalidReport(); } } diff --git a/src/main/java/io/openmanufacturing/ame/web/GenerateResource.java b/src/main/java/io/openmanufacturing/ame/web/GenerateResource.java index b733e7d6..cd1b536a 100644 --- a/src/main/java/io/openmanufacturing/ame/web/GenerateResource.java +++ b/src/main/java/io/openmanufacturing/ame/web/GenerateResource.java @@ -20,9 +20,11 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import io.openmanufacturing.ame.services.GenerateService; +import io.openmanufacturing.sds.aspectmodel.generator.openapi.PagingOption; /** * Controller class that supports the generation of the aspect model into other formats. @@ -40,23 +42,23 @@ public GenerateResource( final GenerateService generateService ) { /** * This Method is used to generate a documentation of the aspect model * - * @param aspectModel - The Aspect Model Data + * @param aspectModel the Aspect Model Data * @return the aspect model definition as documentation html file. */ @PostMapping( "documentation" ) public ResponseEntity generateHtml( @RequestBody final String aspectModel ) throws IOException { - return ResponseEntity.ok( generateService.generateHtmlDocument( aspectModel, Optional.empty() ) ); + return ResponseEntity.ok( generateService.generateHtmlDocument( aspectModel ) ); } /** * This Method is used to generate a sample JSON Payload of the aspect model * - * @param aspectModel The Aspect Model Data + * @param aspectModel the Aspect Model Data * @return The JSON Sample Payload */ @PostMapping( "json-sample" ) public ResponseEntity jsonSample( @RequestBody final String aspectModel ) { - return ResponseEntity.ok( generateService.sampleJSONPayload( aspectModel, Optional.empty() ) ); + return ResponseEntity.ok( generateService.sampleJSONPayload( aspectModel ) ); } /** @@ -67,6 +69,38 @@ public ResponseEntity jsonSample( @RequestBody final String aspectModel */ @PostMapping( "json-schema" ) public ResponseEntity jsonSchema( @RequestBody final String aspectModel ) { - return ResponseEntity.ok( generateService.jsonSchema( aspectModel, Optional.empty() ) ); + return ResponseEntity.ok( generateService.jsonSchema( aspectModel ) ); + } + + /** + * This method is used to generate an OpenAPI specification of the Aspect Model + * + * @param aspectModel the Aspect Model Data + * @param output of the OpenAPI specification + * @param baseUrl the base URL for the Aspect API + * @param includeQueryApi if set to true, a path section for the Query API Endpoint of the Aspect API will be + * included in the specification + * @param useSemanticVersion if set to true, the complete semantic version of the Aspect Model will be used as + * the version of the API, otherwise only the major part of the Aspect Version is used as the version of the + * API. + * @param pagingOption if defined, the chosen paging type will be in the JSON. + * @return The OpenAPI specification + */ + @PostMapping( "open-api-spec" ) + public ResponseEntity openApiSpec( @RequestBody final String aspectModel, + @RequestParam( name = "output", defaultValue = "yaml" ) final String output, + @RequestParam( name = "baseUrl", defaultValue = "https://open-manufacturing.org" ) final String baseUrl, + @RequestParam( name = "includeQueryApi", defaultValue = "false" ) final boolean includeQueryApi, + @RequestParam( name = "useSemanticVersion", defaultValue = "false" ) final boolean useSemanticVersion, + @RequestParam( name = "pagingOption", defaultValue = "TIME_BASED_PAGING" ) + final Optional pagingOption ) { + + final String openApiOutput = output.equals( "json" ) ? + generateService.generateJsonOpenApiSpec( aspectModel, baseUrl, includeQueryApi, useSemanticVersion, + pagingOption ) : + generateService.generateYamlOpenApiSpec( aspectModel, baseUrl, includeQueryApi, useSemanticVersion, + pagingOption ); + + return ResponseEntity.ok( openApiOutput ); } } diff --git a/src/test/java/io/openmanufacturing/ame/services/GenerateServiceTest.java b/src/test/java/io/openmanufacturing/ame/services/GenerateServiceTest.java index 499dc55c..09a78b55 100644 --- a/src/test/java/io/openmanufacturing/ame/services/GenerateServiceTest.java +++ b/src/test/java/io/openmanufacturing/ame/services/GenerateServiceTest.java @@ -31,6 +31,7 @@ import org.springframework.test.context.junit4.SpringRunner; import io.openmanufacturing.ame.repository.strategy.LocalFolderResolverStrategy; +import io.openmanufacturing.sds.aspectmodel.generator.openapi.PagingOption; @RunWith( SpringRunner.class ) @SpringBootTest @@ -55,7 +56,7 @@ public void testAspectModelJsonSample() throws IOException { .thenReturn( storagePath.toString() ); final String payload = generateService.sampleJSONPayload( - Files.readString( storagePath, StandardCharsets.UTF_8 ), Optional.empty() ); + Files.readString( storagePath, StandardCharsets.UTF_8 ) ); assertEquals( "{\"property\":\"eOMtThyhVNLWUZNRcBaQKxI\"}", payload ); } } @@ -70,8 +71,48 @@ public void testAspectModelJsonSchema() throws IOException { .thenReturn( storagePath.toString() ); final String payload = generateService.jsonSchema( - Files.readString( storagePath, StandardCharsets.UTF_8 ), Optional.empty() ); + Files.readString( storagePath, StandardCharsets.UTF_8 ) ); assertTrue( payload.contains( "#/components/schemas/urn_bamm_io.openmanufacturing_1.0.0_Characteristic" ) ); } } + + @Test + public void testAspectModelJsonOpenApiSpec() throws IOException { + try ( final MockedStatic utilities = Mockito.mockStatic( + LocalFolderResolverStrategy.class ) ) { + final Path storagePath = Path.of( openManufacturingTestPath.toString(), aspectModelFile ); + + utilities.when( () -> LocalFolderResolverStrategy.transformToValidModelDirectory( any() ) ) + .thenReturn( storagePath.toString() ); + + final String payload = generateService.generateJsonOpenApiSpec( + Files.readString( storagePath, StandardCharsets.UTF_8 ), "https://test.com", false, false, + Optional.of( PagingOption.TIME_BASED_PAGING ) ); + + assertTrue( payload.contains( "\"openapi\" : \"3.0.3\"" ) ); + assertTrue( payload.contains( "\"version\" : \"v1\"" ) ); + assertTrue( payload.contains( "\"title\" : \"AspectModel\"" ) ); + assertTrue( payload.contains( "\"url\" : \"https://test.com/api/v1\"" ) ); + } + } + + @Test + public void testAspectModelYamlOpenApiSpec() throws IOException { + try ( final MockedStatic utilities = Mockito.mockStatic( + LocalFolderResolverStrategy.class ) ) { + final Path storagePath = Path.of( openManufacturingTestPath.toString(), aspectModelFile ); + + utilities.when( () -> LocalFolderResolverStrategy.transformToValidModelDirectory( any() ) ) + .thenReturn( storagePath.toString() ); + + final String payload = generateService.generateYamlOpenApiSpec( + Files.readString( storagePath, StandardCharsets.UTF_8 ), "https://test.com", false, false, + Optional.of( PagingOption.TIME_BASED_PAGING ) ); + + assertTrue( payload.contains( "openapi: 3.0.3" ) ); + assertTrue( payload.contains( "title: AspectModel" ) ); + assertTrue( payload.contains( "version: v1" ) ); + assertTrue( payload.contains( "url: https://test.com/api/v1" ) ); + } + } }