From bf6be420542d78f7f7865d06927d750c34629cd2 Mon Sep 17 00:00:00 2001 From: Akram Yakubov Date: Fri, 23 Feb 2024 11:31:56 -0800 Subject: [PATCH] Support openapi http proxy using openapi.yaml (#778) --- cloud/docker-image/pom.xml | 6 + .../src/main/docker/zpm.json.template | 1 + incubator/binding-openapi.spec/COPYRIGHT | 13 + incubator/binding-openapi.spec/LICENSE | 114 ++ incubator/binding-openapi.spec/NOTICE | 25 + .../binding-openapi.spec/NOTICE.template | 18 + incubator/binding-openapi.spec/mvnw | 310 +++ incubator/binding-openapi.spec/mvnw.cmd | 182 ++ incubator/binding-openapi.spec/pom.xml | 166 ++ .../binding/openapi/OpenapiFunctions.java | 91 + .../specs/binding/openapi/OpenapiSpecs.java | 23 + .../src/main/moditect/module-info.java | 19 + ...kaazing.k3po.lang.el.spi.FunctionMapperSpi | 1 + .../main/resources/META-INF/zilla/openapi.idl | 28 + .../specs/binding/openapi/config/client.yaml | 28 + .../openapi/config/openapi/petstore.yaml | 136 ++ .../binding/openapi/config/server-secure.yaml | 45 + .../specs/binding/openapi/config/server.yaml | 26 + .../openapi/schema/openapi.3.0.schema.json | 1666 +++++++++++++++++ .../openapi/schema/openapi.3.1.schema.json | 1417 ++++++++++++++ .../openapi/schema/openapi.schema.patch.json | 125 ++ .../streams/http/create.pet/client.rpt | 43 + .../streams/http/create.pet/server.rpt | 47 + .../reject.non.composite.origin/client.rpt | 31 + .../reject.non.composite.origin/server.rpt | 23 + .../streams/openapi/create.pet/client.rpt | 50 + .../streams/openapi/create.pet/server.rpt | 54 + .../binding/openapi/config/SchemaTest.java | 52 + .../internal/OpenapiFunctionsTest.java | 65 + .../specs/binding/openapi/streams/HttpIT.java | 59 + .../binding/openapi/streams/OpenapiIT.java | 49 + incubator/binding-openapi/COPYRIGHT | 12 + incubator/binding-openapi/LICENSE | 114 ++ incubator/binding-openapi/NOTICE | 22 + incubator/binding-openapi/NOTICE.template | 13 + incubator/binding-openapi/mvnw | 310 +++ incubator/binding-openapi/mvnw.cmd | 182 ++ incubator/binding-openapi/pom.xml | 293 +++ .../binding/openapi/config/OpenapiConfig.java | 31 + .../openapi/config/OpenapiOptionsConfig.java | 54 + .../config/OpenpaiOptionsConfigBuilder.java | 86 + .../openapi/internal/OpenapiBinding.java | 67 + .../internal/OpenapiBindingContext.java | 77 + .../internal/OpenapiBindingFactorySpi.java | 36 + .../internal/OpenapiConfiguration.java | 41 + .../internal/config/OpenapiBindingConfig.java | 251 +++ .../OpenapiClientCompositeBindingAdapter.java | 255 +++ .../OpenapiCompositeBindingAdapter.java | 56 + .../config/OpenapiOptionsConfigAdapter.java | 267 +++ .../internal/config/OpenapiRouteConfig.java | 39 + .../OpenapiServerCompositeBindingAdapter.java | 495 +++++ .../openapi/internal/model/OpenApi.java | 26 + .../internal/model/OpenApiBearerAuth.java | 20 + .../internal/model/OpenApiComponents.java | 23 + .../openapi/internal/model/OpenApiHeader.java | 20 + .../openapi/internal/model/OpenApiItem.java | 21 + .../internal/model/OpenApiMediaType.java | 20 + .../internal/model/OpenApiOperation.java | 27 + .../internal/model/OpenApiParameter.java | 23 + .../internal/model/OpenApiRequestBody.java | 22 + .../internal/model/OpenApiResponse.java | 20 + .../openapi/internal/model/OpenApiSchema.java | 31 + .../internal/model/OpenApiSecurityScheme.java | 20 + .../openapi/internal/model/OpenApiServer.java | 20 + .../openapi/internal/model/PathItem.java | 27 + .../internal/model/ResponseByContentType.java | 23 + .../streams/OpenapiClientFactory.java | 1066 +++++++++++ .../streams/OpenapiServerFactory.java | 1070 +++++++++++ .../internal/streams/OpenapiState.java | 134 ++ .../streams/OpenapiStreamFactory.java | 27 + .../internal/view/OpenApiOperationView.java | 70 + .../internal/view/OpenApiOperationsView.java | 73 + .../internal/view/OpenApiPathView.java | 65 + .../internal/view/OpenApiResolvable.java | 47 + .../internal/view/OpenApiSchemaView.java | 86 + .../internal/view/OpenApiServerView.java | 41 + .../src/main/moditect/module-info.java | 39 + ...a.runtime.engine.binding.BindingFactorySpi | 1 + ...e.engine.config.CompositeBindingAdapterSpi | 1 + ...time.engine.config.OptionsConfigAdapterSpi | 1 + .../internal/OpenapiConfigurationTest.java | 31 + .../OpenapiOptionsConfigAdapterTest.java | 145 ++ .../internal/streams/OpenapiClientIT.java | 65 + .../internal/streams/OpenapiServerIT.java | 81 + incubator/pom.xml | 7 + .../http/config/HttpAuthorizationConfig.java | 8 + .../HttpAuthorizationConfigBuilder.java | 27 +- .../http/config/HttpOptionsConfigBuilder.java | 2 +- .../http/schema/http.schema.patch.json | 172 +- .../specs/engine/schema/engine.schema.json | 6 + 90 files changed, 11126 insertions(+), 96 deletions(-) create mode 100644 incubator/binding-openapi.spec/COPYRIGHT create mode 100644 incubator/binding-openapi.spec/LICENSE create mode 100644 incubator/binding-openapi.spec/NOTICE create mode 100644 incubator/binding-openapi.spec/NOTICE.template create mode 100755 incubator/binding-openapi.spec/mvnw create mode 100644 incubator/binding-openapi.spec/mvnw.cmd create mode 100644 incubator/binding-openapi.spec/pom.xml create mode 100644 incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java create mode 100644 incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiSpecs.java create mode 100644 incubator/binding-openapi.spec/src/main/moditect/module-info.java create mode 100644 incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi create mode 100644 incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/openapi/petstore.yaml create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/client.rpt create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/server.rpt create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/client.rpt create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/server.rpt create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt create mode 100644 incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/config/SchemaTest.java create mode 100644 incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java create mode 100644 incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/HttpIT.java create mode 100644 incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/OpenapiIT.java create mode 100644 incubator/binding-openapi/COPYRIGHT create mode 100644 incubator/binding-openapi/LICENSE create mode 100644 incubator/binding-openapi/NOTICE create mode 100644 incubator/binding-openapi/NOTICE.template create mode 100755 incubator/binding-openapi/mvnw create mode 100644 incubator/binding-openapi/mvnw.cmd create mode 100644 incubator/binding-openapi/pom.xml create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiOptionsConfig.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenpaiOptionsConfigBuilder.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBinding.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingContext.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingFactorySpi.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfiguration.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiCompositeBindingAdapter.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiRouteConfig.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApi.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiBearerAuth.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiComponents.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiHeader.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiItem.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiMediaType.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiParameter.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiRequestBody.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiResponse.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSchema.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSecurityScheme.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiServer.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/PathItem.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/ResponseByContentType.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientFactory.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiState.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiStreamFactory.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiResolvable.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiSchemaView.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiServerView.java create mode 100644 incubator/binding-openapi/src/main/moditect/module-info.java create mode 100644 incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi create mode 100644 incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi create mode 100644 incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi create mode 100644 incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfigurationTest.java create mode 100644 incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java create mode 100644 incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientIT.java create mode 100644 incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java diff --git a/cloud/docker-image/pom.xml b/cloud/docker-image/pom.xml index 280ead1a1d..a02ca90ede 100644 --- a/cloud/docker-image/pom.xml +++ b/cloud/docker-image/pom.xml @@ -49,6 +49,12 @@ ${project.version} runtime + + ${project.groupId} + binding-openapi + ${project.version} + runtime + ${project.groupId} binding-echo diff --git a/cloud/docker-image/src/main/docker/zpm.json.template b/cloud/docker-image/src/main/docker/zpm.json.template index 4b2d69bc63..db0233de50 100644 --- a/cloud/docker-image/src/main/docker/zpm.json.template +++ b/cloud/docker-image/src/main/docker/zpm.json.template @@ -27,6 +27,7 @@ "io.aklivity.zilla:binding-kafka", "io.aklivity.zilla:binding-mqtt", "io.aklivity.zilla:binding-mqtt-kafka", + "io.aklivity.zilla:binding-openapi", "io.aklivity.zilla:binding-proxy", "io.aklivity.zilla:binding-sse", "io.aklivity.zilla:binding-sse-kafka", diff --git a/incubator/binding-openapi.spec/COPYRIGHT b/incubator/binding-openapi.spec/COPYRIGHT new file mode 100644 index 0000000000..8b1b7215ef --- /dev/null +++ b/incubator/binding-openapi.spec/COPYRIGHT @@ -0,0 +1,13 @@ +Copyright ${copyrightYears} Aklivity Inc. + +Aklivity licenses this file to you under the Apache License, +version 2.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. diff --git a/incubator/binding-openapi.spec/LICENSE b/incubator/binding-openapi.spec/LICENSE new file mode 100644 index 0000000000..1184b83429 --- /dev/null +++ b/incubator/binding-openapi.spec/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. diff --git a/incubator/binding-openapi.spec/NOTICE b/incubator/binding-openapi.spec/NOTICE new file mode 100644 index 0000000000..3936d236bc --- /dev/null +++ b/incubator/binding-openapi.spec/NOTICE @@ -0,0 +1,25 @@ +Licensed under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + agrona under The Apache License, Version 2.0 + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::specs::binding-http.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-proxy.spec under The Apache Software License, Version 2.0 + zilla::specs::engine.spec under The Apache Software License, Version 2.0 + + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi.spec/NOTICE.template b/incubator/binding-openapi.spec/NOTICE.template new file mode 100644 index 0000000000..e9ed8f0e7b --- /dev/null +++ b/incubator/binding-openapi.spec/NOTICE.template @@ -0,0 +1,18 @@ +Licensed under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: +#GENERATED_NOTICES# + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ \ No newline at end of file diff --git a/incubator/binding-openapi.spec/mvnw b/incubator/binding-openapi.spec/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-openapi.spec/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-openapi.spec/mvnw.cmd b/incubator/binding-openapi.spec/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-openapi.spec/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-openapi.spec/pom.xml b/incubator/binding-openapi.spec/pom.xml new file mode 100644 index 0000000000..d1a29c34c9 --- /dev/null +++ b/incubator/binding-openapi.spec/pom.xml @@ -0,0 +1,166 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + develop-SNAPSHOT + ../pom.xml + + + binding-openapi.spec + zilla::incubator::binding-openapi.spec + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 11 + 11 + 0.96 + 1 + + + + + org.kaazing + k3po.lang + provided + + + ${project.groupId} + engine.spec + ${project.version} + + + ${project.groupId} + binding-http.spec + ${project.version} + + + junit + junit + test + + + org.kaazing + k3po.junit + test + + + org.hamcrest + hamcrest-library + test + + + + + + + src/main/resources + + + src/main/scripts + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http openapi + io.aklivity.zilla.specs.binding.openapi.internal.types + + + + + validate + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/specs/binding/openapi/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + + diff --git a/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java new file mode 100644 index 0000000000..5dca05c51b --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java @@ -0,0 +1,91 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.kaazing.k3po.lang.el.Function; +import org.kaazing.k3po.lang.el.spi.FunctionMapperSpi; + +import io.aklivity.zilla.specs.binding.openapi.internal.types.stream.OpenapiBeginExFW; + +public final class OpenapiFunctions +{ + @Function + public static OpenapiBeginExBuilder beginEx() + { + return new OpenapiBeginExBuilder(); + } + + public static final class OpenapiBeginExBuilder + { + private final OpenapiBeginExFW.Builder beginExRW; + + private OpenapiBeginExBuilder() + { + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1024 * 8]); + this.beginExRW = new OpenapiBeginExFW.Builder().wrap(writeBuffer, 0, writeBuffer.capacity()); + } + + public OpenapiBeginExBuilder typeId( + int typeId) + { + beginExRW.typeId(typeId); + return this; + } + + public OpenapiBeginExBuilder operationId( + String operationId) + { + beginExRW.operationId(operationId); + return this; + } + + public OpenapiBeginExBuilder extension( + byte[] extension) + { + beginExRW.extension(e -> e.set(extension)); + return this; + } + + public byte[] build() + { + final OpenapiBeginExFW beginEx = beginExRW.build(); + final byte[] array = new byte[beginEx.sizeof()]; + beginEx.buffer().getBytes(beginEx.offset(), array); + return array; + } + } + + public static class Mapper extends FunctionMapperSpi.Reflective + { + public Mapper() + { + super(OpenapiFunctions.class); + } + + @Override + public String getPrefixName() + { + return "openapi"; + } + } + + private OpenapiFunctions() + { + // utility + } +} diff --git a/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiSpecs.java b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiSpecs.java new file mode 100644 index 0000000000..3faa1eb489 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiSpecs.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi; + +public final class OpenapiSpecs +{ + private OpenapiSpecs() + { + } +} diff --git a/incubator/binding-openapi.spec/src/main/moditect/module-info.java b/incubator/binding-openapi.spec/src/main/moditect/module-info.java new file mode 100644 index 0000000000..a85321fdd7 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/moditect/module-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +open module io.aklivity.zilla.specs.binding.openapi +{ + requires transitive io.aklivity.zilla.specs.engine; +} diff --git a/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi b/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi new file mode 100644 index 0000000000..cb5c82fd24 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi @@ -0,0 +1 @@ +io.aklivity.zilla.specs.binding.openapi.OpenapiFunctions$Mapper diff --git a/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl b/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl new file mode 100644 index 0000000000..5a24b610ff --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +scope openapi +{ + + scope stream + { + struct OpenapiBeginEx extends core::stream::Extension + { + int32 apiId = 0; + string16 operationId = null; + octets extension; + } + } +} diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml new file mode 100644 index 0000000000..954302a886 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml @@ -0,0 +1,28 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + openapi0: + type: openapi + kind: client + options: + tcp: + host: localhost + port: 8080 + specs: + - openapi/petstore.yaml diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/openapi/petstore.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/openapi/petstore.yaml new file mode 100644 index 0000000000..8885f2c6b9 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/openapi/petstore.yaml @@ -0,0 +1,136 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:8080 + - url: https://localhost:9090 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + maximum: 100 + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{id}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: id + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + maxItems: 100 + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml new file mode 100644 index 0000000000..9dd4d71306 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml @@ -0,0 +1,45 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + server: + type: test +bindings: + composite0: + type: openapi + kind: server + vault: server + options: + tls: + keys: + - localhost + alpn: + - protocol2 + http: + authorization: + test0: + credentials: + cookies: + access_token: "{credentials}" + headers: + authorization: Bearer {credentials} + query: + access_token: "{credentials}" + specs: + - openapi/petstore.yaml + exit: openapi0 diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml new file mode 100644 index 0000000000..ce6e1fc0ee --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + composite0: + type: openapi + kind: server + options: + specs: + - openapi/petstore.yaml + exit: openapi0 diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json new file mode 100644 index 0000000000..6a175a4f63 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json @@ -0,0 +1,1666 @@ +{ + "id": "https://spec.openapis.org/oas/3.0/schema/2021-09-28", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "The description of OpenAPI v3.0.x documents, as defined by https://spec.openapis.org/oas/v3.0.3", + "type": "object", + "required": [ + "openapi", + "info", + "paths" + ], + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.0\\.\\d(-.+)?$" + }, + "info": { + "$ref": "#/definitions/Info" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + }, + "uniqueItems": true + }, + "paths": { + "$ref": "#/definitions/Paths" + }, + "components": { + "$ref": "#/definitions/Components" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "definitions": { + "Reference": { + "type": "object", + "required": [ + "$ref" + ], + "patternProperties": { + "^\\$ref$": { + "type": "string", + "format": "uri-reference" + } + } + }, + "Info": { + "type": "object", + "required": [ + "title", + "version" + ], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string", + "format": "uri-reference" + }, + "contact": { + "$ref": "#/definitions/Contact" + }, + "license": { + "$ref": "#/definitions/License" + }, + "version": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Contact": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "License": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Server": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ServerVariable" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ServerVariable": { + "type": "object", + "required": [ + "default" + ], + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + } + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Components": { + "type": "object", + "properties": { + "schemas": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "responses": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Response" + } + ] + } + } + }, + "parameters": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Parameter" + } + ] + } + } + }, + "examples": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Example" + } + ] + } + } + }, + "requestBodies": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/RequestBody" + } + ] + } + } + }, + "headers": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Header" + } + ] + } + } + }, + "securitySchemes": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/SecurityScheme" + } + ] + } + } + }, + "links": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Link" + } + ] + } + } + }, + "callbacks": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Callback" + } + ] + } + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + }, + "minProperties": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "required": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "enum": { + "type": "array", + "items": { + }, + "minItems": 1, + "uniqueItems": false + }, + "type": { + "type": "string", + "enum": [ + "array", + "boolean", + "integer", + "number", + "object", + "string" + ] + }, + "not": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "allOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "oneOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "anyOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "properties": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + }, + { + "type": "boolean" + } + ], + "default": true + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "default": { + }, + "nullable": { + "type": "boolean", + "default": false + }, + "discriminator": { + "$ref": "#/definitions/Discriminator" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "example": { + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/XML" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Discriminator": { + "type": "object", + "required": [ + "propertyName" + ], + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "XML": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "uri" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Response": { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Header" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Link" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "MediaType": { + "type": "object", + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Encoding" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + } + ] + }, + "Example": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": { + }, + "externalValue": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Header": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "simple" + ], + "default": "simple" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + }, + { + "$ref": "#/definitions/SchemaXORContent" + } + ] + }, + "Paths": { + "type": "object", + "patternProperties": { + "^\\/": { + "$ref": "#/definitions/PathItem" + }, + "^x-": { + } + }, + "additionalProperties": false + }, + "PathItem": { + "type": "object", + "properties": { + "$ref": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "uniqueItems": true + } + }, + "patternProperties": { + "^(get|put|post|delete|options|head|patch|trace)$": { + "$ref": "#/definitions/Operation" + }, + "^x-": { + } + }, + "additionalProperties": false + }, + "Operation": { + "type": "object", + "required": [ + "responses" + ], + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "uniqueItems": true + }, + "requestBody": { + "oneOf": [ + { + "$ref": "#/definitions/RequestBody" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "responses": { + "$ref": "#/definitions/Responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Callback" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Responses": { + "type": "object", + "properties": { + "default": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "patternProperties": { + "^[1-5](?:\\d{2}|XX)$": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "^x-": { + } + }, + "minProperties": 1, + "additionalProperties": false + }, + "SecurityRequirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "Tag": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ExternalDocumentation": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ExampleXORExamples": { + "description": "Example and examples are mutually exclusive", + "not": { + "required": [ + "example", + "examples" + ] + } + }, + "SchemaXORContent": { + "description": "Schema and content are mutually exclusive, at least one is required", + "not": { + "required": [ + "schema", + "content" + ] + }, + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ], + "description": "Some properties are not allowed if content is present", + "allOf": [ + { + "not": { + "required": [ + "style" + ] + } + }, + { + "not": { + "required": [ + "explode" + ] + } + }, + { + "not": { + "required": [ + "allowReserved" + ] + } + }, + { + "not": { + "required": [ + "example" + ] + } + }, + { + "not": { + "required": [ + "examples" + ] + } + } + ] + } + ] + }, + "Parameter": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "required": [ + "name", + "in" + ], + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + }, + { + "$ref": "#/definitions/SchemaXORContent" + }, + { + "$ref": "#/definitions/ParameterLocation" + } + ] + }, + "ParameterLocation": { + "description": "Parameter location", + "oneOf": [ + { + "description": "Parameter in path", + "required": [ + "required" + ], + "properties": { + "in": { + "enum": [ + "path" + ] + }, + "style": { + "enum": [ + "matrix", + "label", + "simple" + ], + "default": "simple" + }, + "required": { + "enum": [ + true + ] + } + } + }, + { + "description": "Parameter in query", + "properties": { + "in": { + "enum": [ + "query" + ] + }, + "style": { + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ], + "default": "form" + } + } + }, + { + "description": "Parameter in header", + "properties": { + "in": { + "enum": [ + "header" + ] + }, + "style": { + "enum": [ + "simple" + ], + "default": "simple" + } + } + }, + { + "description": "Parameter in cookie", + "properties": { + "in": { + "enum": [ + "cookie" + ] + }, + "style": { + "enum": [ + "form" + ], + "default": "form" + } + } + } + ] + }, + "RequestBody": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "description": { + "type": "string" + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "required": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "SecurityScheme": { + "oneOf": [ + { + "$ref": "#/definitions/APIKeySecurityScheme" + }, + { + "$ref": "#/definitions/HTTPSecurityScheme" + }, + { + "$ref": "#/definitions/OAuth2SecurityScheme" + }, + { + "$ref": "#/definitions/OpenIdConnectSecurityScheme" + } + ] + }, + "APIKeySecurityScheme": { + "type": "object", + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ] + }, + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "cookie" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "HTTPSecurityScheme": { + "type": "object", + "required": [ + "scheme", + "type" + ], + "properties": { + "scheme": { + "type": "string" + }, + "bearerFormat": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "http" + ] + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "oneOf": [ + { + "description": "Bearer", + "properties": { + "scheme": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + } + }, + { + "description": "Non Bearer", + "not": { + "required": [ + "bearerFormat" + ] + }, + "properties": { + "scheme": { + "not": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + } + } + } + ] + }, + "OAuth2SecurityScheme": { + "type": "object", + "required": [ + "type", + "flows" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flows": { + "$ref": "#/definitions/OAuthFlows" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "OpenIdConnectSecurityScheme": { + "type": "object", + "required": [ + "type", + "openIdConnectUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "openIdConnect" + ] + }, + "openIdConnectUrl": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "OAuthFlows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/definitions/ImplicitOAuthFlow" + }, + "password": { + "$ref": "#/definitions/PasswordOAuthFlow" + }, + "clientCredentials": { + "$ref": "#/definitions/ClientCredentialsFlow" + }, + "authorizationCode": { + "$ref": "#/definitions/AuthorizationCodeOAuthFlow" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ImplicitOAuthFlow": { + "type": "object", + "required": [ + "authorizationUrl", + "scopes" + ], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "PasswordOAuthFlow": { + "type": "object", + "required": [ + "tokenUrl", + "scopes" + ], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ClientCredentialsFlow": { + "type": "object", + "required": [ + "tokenUrl", + "scopes" + ], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "AuthorizationCodeOAuthFlow": { + "type": "object", + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri-reference" + }, + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Link": { + "type": "object", + "properties": { + "operationId": { + "type": "string" + }, + "operationRef": { + "type": "string", + "format": "uri-reference" + }, + "parameters": { + "type": "object", + "additionalProperties": { + } + }, + "requestBody": { + }, + "description": { + "type": "string" + }, + "server": { + "$ref": "#/definitions/Server" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "not": { + "description": "Operation Id and Operation Ref are mutually exclusive", + "required": [ + "operationId", + "operationRef" + ] + } + }, + "Callback": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PathItem" + }, + "patternProperties": { + "^x-": { + } + } + }, + "Encoding": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Header" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "style": { + "type": "string", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + } + } +} diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json new file mode 100644 index 0000000000..778b2edfa8 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json @@ -0,0 +1,1417 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/schema/2022-10-07", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "The description of OpenAPI v3.1.x documents without schema validation, as defined by https://spec.openapis.org/oas/v3.1.0", + "type": "object", + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.1\\.\\d+(-.+)?$" + }, + "info": { + "$ref": "#/$defs/info" + }, + "jsonSchemaDialect": { + "type": "string", + "format": "uri", + "default": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + }, + "default": [ + { + "url": "/" + } + ] + }, + "paths": { + "$ref": "#/$defs/paths" + }, + "webhooks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "components": { + "$ref": "#/$defs/components" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/$defs/tag" + } + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "openapi", + "info" + ], + "anyOf": [ + { + "required": [ + "paths" + ] + }, + { + "required": [ + "components" + ] + }, + { + "required": [ + "webhooks" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "$defs": { + "info": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#info-object", + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string", + "format": "uri" + }, + "contact": { + "$ref": "#/$defs/contact" + }, + "license": { + "$ref": "#/$defs/license" + }, + "version": { + "type": "string" + } + }, + "required": [ + "title", + "version" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "contact": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#contact-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "license": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#license-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "name" + ], + "dependentSchemas": { + "identifier": { + "not": { + "required": [ + "url" + ] + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-object", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/server-variable" + } + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server-variable": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-variable-object", + "type": "object", + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "default" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "components": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#components-object", + "type": "object", + "properties": { + "schemas": { + "type": "object", + "additionalProperties": { + "$dynamicRef": "#meta" + } + }, + "responses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/response-or-reference" + } + }, + "parameters": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + }, + "requestBodies": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/request-body-or-reference" + } + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "securitySchemes": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/security-scheme-or-reference" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "pathItems": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + } + }, + "patternProperties": { + "^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": { + "$comment": "Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected", + "propertyNames": { + "pattern": "^[a-zA-Z0-9._-]+$" + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "paths": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#paths-object", + "type": "object", + "patternProperties": { + "^/": { + "$ref": "#/$defs/path-item" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#path-item-object", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "get": { + "$ref": "#/$defs/operation" + }, + "put": { + "$ref": "#/$defs/operation" + }, + "post": { + "$ref": "#/$defs/operation" + }, + "delete": { + "$ref": "#/$defs/operation" + }, + "options": { + "$ref": "#/$defs/operation" + }, + "head": { + "$ref": "#/$defs/operation" + }, + "patch": { + "$ref": "#/$defs/operation" + }, + "trace": { + "$ref": "#/$defs/operation" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/path-item" + } + }, + "operation": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#operation-object", + "type": "object", + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "requestBody": { + "$ref": "#/$defs/request-body-or-reference" + }, + "responses": { + "$ref": "#/$defs/responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "external-documentation": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#external-documentation-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "parameter": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#parameter-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "path", + "cookie" + ] + }, + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + }, + "content": { + "$ref": "#/$defs/content", + "minProperties": 1, + "maxProperties": 1 + } + }, + "required": [ + "name", + "in" + ], + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "if": { + "properties": { + "in": { + "const": "query" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "allowEmptyValue": { + "default": false, + "type": "boolean" + } + } + }, + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "type": "string" + }, + "explode": { + "type": "boolean" + } + }, + "allOf": [ + { + "$ref": "#/$defs/examples" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie" + }, + { + "$ref": "#/$defs/styles-for-form" + } + ], + "$defs": { + "styles-for-path": { + "if": { + "properties": { + "in": { + "const": "path" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "simple", + "enum": [ + "matrix", + "label", + "simple" + ] + }, + "required": { + "const": true + } + }, + "required": [ + "required" + ] + } + }, + "styles-for-header": { + "if": { + "properties": { + "in": { + "const": "header" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "simple", + "const": "simple" + } + } + } + }, + "styles-for-query": { + "if": { + "properties": { + "in": { + "const": "query" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "form", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "allowReserved": { + "default": false, + "type": "boolean" + } + } + } + }, + "styles-for-cookie": { + "if": { + "properties": { + "in": { + "const": "cookie" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "form", + "const": "form" + } + } + } + } + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "parameter-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/parameter" + } + }, + "request-body": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#request-body-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "content": { + "$ref": "#/$defs/content" + }, + "required": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "content" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "request-body-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/request-body" + } + }, + "content": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#fixed-fields-10", + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + }, + "propertyNames": { + "format": "media-range" + } + }, + "media-type": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#media-type-object", + "type": "object", + "properties": { + "schema": { + "$dynamicRef": "#meta" + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/encoding" + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/examples" + } + ], + "unevaluatedProperties": false + }, + "encoding": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#encoding-object", + "type": "object", + "properties": { + "contentType": { + "type": "string", + "format": "media-range" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "style": { + "default": "form", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "default": false, + "type": "boolean" + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/styles-for-form" + } + ], + "unevaluatedProperties": false + }, + "responses": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#responses-object", + "type": "object", + "properties": { + "default": { + "$ref": "#/$defs/response-or-reference" + } + }, + "patternProperties": { + "^[1-5](?:[0-9]{2}|XX)$": { + "$ref": "#/$defs/response-or-reference" + } + }, + "minProperties": 1, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "if": { + "$comment": "either default, or at least one response code property must exist", + "patternProperties": { + "^[1-5](?:[0-9]{2}|XX)$": false + } + }, + "then" : { + "required": [ "default" ] + } + }, + "response": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#response-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "content": { + "$ref": "#/$defs/content" + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + } + }, + "required": [ + "description" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "response-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/response" + } + }, + "callbacks": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#callback-object", + "type": "object", + "$ref": "#/$defs/specification-extensions", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "callbacks-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/callbacks" + } + }, + "example": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#example-object", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": true, + "externalValue": { + "type": "string", + "format": "uri" + } + }, + "not": { + "required": [ + "value", + "externalValue" + ] + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "example-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/example" + } + }, + "link": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#link-object", + "type": "object", + "properties": { + "operationRef": { + "type": "string", + "format": "uri-reference" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "$ref": "#/$defs/map-of-strings" + }, + "requestBody": true, + "description": { + "type": "string" + }, + "body": { + "$ref": "#/$defs/server" + } + }, + "oneOf": [ + { + "required": [ + "operationRef" + ] + }, + { + "required": [ + "operationId" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "link-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/link" + } + }, + "header": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#header-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + }, + "content": { + "$ref": "#/$defs/content", + "minProperties": 1, + "maxProperties": 1 + } + }, + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "default": "simple", + "const": "simple" + }, + "explode": { + "default": false, + "type": "boolean" + } + }, + "$ref": "#/$defs/examples" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "header-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/header" + } + }, + "tag": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#tag-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "name" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "reference": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#reference-object", + "type": "object", + "properties": { + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + "schema": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object", + "$dynamicAnchor": "meta", + "type": [ + "object", + "boolean" + ] + }, + "security-scheme": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-scheme-object", + "type": "object", + "properties": { + "type": { + "enum": [ + "apiKey", + "http", + "mutualTLS", + "oauth2", + "openIdConnect" + ] + }, + "description": { + "type": "string" + } + }, + "required": [ + "type" + ], + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-apikey" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http-bearer" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oauth2" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oidc" + } + ], + "unevaluatedProperties": false, + "$defs": { + "type-apikey": { + "if": { + "properties": { + "type": { + "const": "apiKey" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "cookie" + ] + } + }, + "required": [ + "name", + "in" + ] + } + }, + "type-http": { + "if": { + "properties": { + "type": { + "const": "http" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "scheme": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + } + }, + "type-http-bearer": { + "if": { + "properties": { + "type": { + "const": "http" + }, + "scheme": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + }, + "required": [ + "type", + "scheme" + ] + }, + "then": { + "properties": { + "bearerFormat": { + "type": "string" + } + } + } + }, + "type-oauth2": { + "if": { + "properties": { + "type": { + "const": "oauth2" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "flows": { + "$ref": "#/$defs/oauth-flows" + } + }, + "required": [ + "flows" + ] + } + }, + "type-oidc": { + "if": { + "properties": { + "type": { + "const": "openIdConnect" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "openIdConnectUrl": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "openIdConnectUrl" + ] + } + } + } + }, + "security-scheme-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/security-scheme" + } + }, + "oauth-flows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/$defs/oauth-flows/$defs/implicit" + }, + "password": { + "$ref": "#/$defs/oauth-flows/$defs/password" + }, + "clientCredentials": { + "$ref": "#/$defs/oauth-flows/$defs/client-credentials" + }, + "authorizationCode": { + "$ref": "#/$defs/oauth-flows/$defs/authorization-code" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "$defs": { + "implicit": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "password": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "client-credentials": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "authorization-code": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + } + } + }, + "security-requirement": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-requirement-object", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "specification-extensions": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#specification-extensions", + "patternProperties": { + "^x-": true + } + }, + "examples": { + "properties": { + "example": true, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + } + } + }, + "map-of-strings": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "styles-for-form": { + "if": { + "properties": { + "style": { + "const": "form" + } + }, + "required": [ + "style" + ] + }, + "then": { + "properties": { + "explode": { + "default": true + } + } + }, + "else": { + "properties": { + "explode": { + "default": false + } + } + } + } + } +} diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json new file mode 100644 index 0000000000..8f7161754c --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json @@ -0,0 +1,125 @@ +[ + { + "op": "add", + "path": "/$defs/binding/properties/type/enum/-", + "value": "openapi" + }, + { + "op": "add", + "path": "/$defs/binding/allOf/-", + "value": + { + "if": + { + "properties": + { + "type": + { + "const": "openapi" + } + } + }, + "then": + { + "properties": + { + "type": + { + "const": "openapi" + }, + "kind": + { + "enum": [ "client", "server" ] + }, + "options": + { + "properties": + { + "tcp": "#/$defs/binding/tcp/options", + "tls": "#/$defs/binding/tls/options", + "http": + { + "title": "Http", + "type": "object", + "properties": + { + "authorization": "$defs/options/binding/http/authorization" + }, + "additionalProperties": false + }, + "specs": + { + "title": "Specs", + "type": "array", + "items": + { + "title": "Specs", + "type": "string" + } + } + }, + "additionalProperties": false + }, + "routes": false + }, + "oneOf": + [ + { + "properties": + { + "kind": + { + "const": "server" + } + }, + "anyOf": + [ + { + "required": + [ + "exit" + ] + }, + { + "properties": + { + "routes": + { + "required": + [ + "exit" + ] + } + }, + "required": + [ + "routes" + ] + } + ] + }, + { + "properties": + { + "kind": + { + "const": "client" + }, + "routes": + { + "items": + { + "properties": + { + "exit": false + } + } + }, + "exit": false + } + } + ] + } + } + } +] diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/client.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/client.rpt new file mode 100644 index 0000000000..8e97310066 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/client.rpt @@ -0,0 +1,43 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + option zilla:ephemeral "test:composite0/http" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()} +connected + +write "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()} + +read "{\"code\": 0,\"message\": \"string\"}" diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/server.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/server.rpt new file mode 100644 index 0000000000..d7a53f172b --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/server.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()} + +connected + +read "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()} + +write "{\"code\": 0,\"message\": \"string\"}" +write flush diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/client.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/client.rpt new file mode 100644 index 0000000000..ce2f2bb07f --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/client.rpt @@ -0,0 +1,31 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + option zilla:ephemeral "test:composite0/http" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()} +connect aborted diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/server.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/server.rpt new file mode 100644 index 0000000000..c741d4e588 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/server.rpt @@ -0,0 +1,23 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 12 + option zilla:transmission "half-duplex" + +rejected diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt new file mode 100644 index 0000000000..11a25bae39 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt @@ -0,0 +1,50 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/openapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()) + .build()} +connected + +write "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +write close + +read zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()) + .build()} + +read "{\"code\": 0,\"message\": \"string\"}" diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt new file mode 100644 index 0000000000..c074450d95 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt @@ -0,0 +1,54 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/openapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()) + .build()} + +connected + +read "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +read closed + +write zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()) + .build()} +write flush + +write "{\"code\": 0,\"message\": \"string\"}" +write flush diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/config/SchemaTest.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/config/SchemaTest.java new file mode 100644 index 0000000000..4fdfa3e4c4 --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/config/SchemaTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.JsonObject; + +import org.junit.Rule; +import org.junit.Test; + +import io.aklivity.zilla.specs.engine.config.ConfigSchemaRule; + +public class SchemaTest +{ + @Rule + public final ConfigSchemaRule schema = new ConfigSchemaRule() + .schemaPatch("io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json") + .schemaPatch("io/aklivity/zilla/specs/engine/schema/vault/test.schema.patch.json") + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/config"); + + @Test + public void shouldValidateServer() + { + JsonObject config = schema.validate("server.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateClient() + { + JsonObject config = schema.validate("client.yaml"); + + assertThat(config, not(nullValue())); + } +} diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java new file mode 100644 index 0000000000..ef2ffec766 --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +import java.lang.reflect.Method; + +import javax.el.ELContext; +import javax.el.FunctionMapper; + +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Test; +import org.kaazing.k3po.lang.internal.el.ExpressionContext; + +import io.aklivity.zilla.specs.binding.openapi.OpenapiFunctions; +import io.aklivity.zilla.specs.binding.openapi.internal.types.stream.OpenapiBeginExFW; + + +public class OpenapiFunctionsTest +{ + @Test + public void shouldResolveFunction() throws Exception + { + final ELContext ctx = new ExpressionContext(); + final FunctionMapper mapper = ctx.getFunctionMapper(); + final Method function = mapper.resolveFunction("openapi", "beginEx"); + + assertNotNull(function); + assertSame(OpenapiFunctions.class, function.getDeclaringClass()); + } + + @Test + public void shouldGenerateBeginExtension() + { + byte[] build = OpenapiFunctions.beginEx() + .typeId(0x01) + .operationId("test") + .extension("extension".getBytes()) + .build(); + + DirectBuffer buffer = new UnsafeBuffer(build); + + OpenapiBeginExFW beginEx = new OpenapiBeginExFW().wrap(buffer, 0, buffer.capacity()); + assertEquals(0L, beginEx.apiId()); + assertEquals("test", beginEx.operationId().asString()); + assertEquals("extension".length(), beginEx.extension().sizeof()); + } +} diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/HttpIT.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/HttpIT.java new file mode 100644 index 0000000000..2595d2c2ca --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/HttpIT.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class HttpIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/openapi/streams/http"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${http}/create.pet/client", + "${http}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${http}/reject.non.composite.origin/client", + "${http}/reject.non.composite.origin/server" + }) + public void shouldRejectNonCompositeOrigin() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/OpenapiIT.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/OpenapiIT.java new file mode 100644 index 0000000000..66b019bc53 --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/OpenapiIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class OpenapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/streams/openapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${openapi}/create.pet/client", + "${openapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi/COPYRIGHT b/incubator/binding-openapi/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/binding-openapi/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/binding-openapi/LICENSE b/incubator/binding-openapi/LICENSE new file mode 100644 index 0000000000..f6abb6327b --- /dev/null +++ b/incubator/binding-openapi/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/binding-openapi/NOTICE b/incubator/binding-openapi/NOTICE new file mode 100644 index 0000000000..81c0ad0dcf --- /dev/null +++ b/incubator/binding-openapi/NOTICE @@ -0,0 +1,22 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::incubator::catalog-inline under Aklivity Community License Agreement + zilla::incubator::model-core under Aklivity Community License Agreement + zilla::incubator::model-json under Aklivity Community License Agreement + zilla::runtime::binding-http under The Apache Software License, Version 2.0 + zilla::runtime::binding-tcp under The Apache Software License, Version 2.0 + zilla::runtime::binding-tls under The Apache Software License, Version 2.0 + diff --git a/incubator/binding-openapi/NOTICE.template b/incubator/binding-openapi/NOTICE.template new file mode 100644 index 0000000000..209ca12f74 --- /dev/null +++ b/incubator/binding-openapi/NOTICE.template @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# diff --git a/incubator/binding-openapi/mvnw b/incubator/binding-openapi/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-openapi/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-openapi/mvnw.cmd b/incubator/binding-openapi/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-openapi/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-openapi/pom.xml b/incubator/binding-openapi/pom.xml new file mode 100644 index 0000000000..0d4ce30338 --- /dev/null +++ b/incubator/binding-openapi/pom.xml @@ -0,0 +1,293 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + develop-SNAPSHOT + ../pom.xml + + + binding-openapi + zilla::incubator::binding-openapi + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 0.60 + 4 + + + + + ${project.groupId} + binding-openapi.spec + ${project.version} + provided + + + ${project.groupId} + engine + ${project.version} + provided + + + io.aklivity.zilla + binding-http + ${project.version} + + + io.aklivity.zilla + binding-tcp + ${project.version} + + + io.aklivity.zilla + binding-tls + ${project.version} + + + io.aklivity.zilla + catalog-inline + ${project.version} + + + io.aklivity.zilla + model-core + ${project.version} + + + io.aklivity.zilla + model-json + ${project.version} + + + ${project.groupId} + engine + test-jar + ${project.version} + test + + + junit + junit + test + + + org.hamcrest + hamcrest + test + + + com.vtence.hamcrest + hamcrest-jpa + test + + + com.github.npathai + hamcrest-optional + test + + + org.mockito + mockito-core + test + + + org.kaazing + k3po.junit + test + + + org.kaazing + k3po.lang + test + + + org.openjdk.jmh + jmh-core + test + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + + + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http openapi + io.aklivity.zilla.runtime.binding.openapi.internal.types + + + + + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + maven-dependency-plugin + + + process-resources + + unpack + + + + + ${project.groupId} + binding-openapi.spec + + + ^\Qio/aklivity/zilla/specs/binding/openapi/\E + io/aklivity/zilla/runtime/binding/openapi/internal/ + + + + + io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json, + io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json, + io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json + + ${project.build.directory}/classes + + + + unpack-openapi + generate-sources + + unpack + + + + + ${project.groupId} + binding-openapi.spec + ${project.version} + ${basedir}/target/test-classes + **\/*.yaml + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/runtime/binding/openapi/internal/types/**/*.class + io/aklivity/zilla/runtime/binding/openapi/internal/model/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + io.gatling + maven-shade-plugin + + + + org.agrona:agrona + io.aklivity.zilla:engine + org.openjdk.jmh:jmh-core + net.sf.jopt-simple:jopt-simple + org.apache.commons:commons-math3 + commons-cli:commons-cli + com.github.biboudis:jmh-profilers + + + + + + + diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java new file mode 100644 index 0000000000..dd2239e5e8 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.config; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; + +public class OpenapiConfig +{ + public final String location; + public final OpenApi openapi; + + public OpenapiConfig( + String location, + OpenApi openapi) + { + this.location = location; + this.openapi = openapi; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiOptionsConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiOptionsConfig.java new file mode 100644 index 0000000000..1846572096 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiOptionsConfig.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class OpenapiOptionsConfig extends OptionsConfig +{ + public final TcpOptionsConfig tcp; + public final TlsOptionsConfig tls; + public final HttpOptionsConfig http; + public final List openapis; + + public static OpenpaiOptionsConfigBuilder builder() + { + return new OpenpaiOptionsConfigBuilder<>(OpenapiOptionsConfig.class::cast); + } + + public static OpenpaiOptionsConfigBuilder builder( + Function mapper) + { + return new OpenpaiOptionsConfigBuilder<>(mapper); + } + + public OpenapiOptionsConfig( + TcpOptionsConfig tcp, + TlsOptionsConfig tls, + HttpOptionsConfig http, + List openapis) + { + this.tcp = tcp; + this.tls = tls; + this.http = http; + this.openapis = openapis; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenpaiOptionsConfigBuilder.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenpaiOptionsConfigBuilder.java new file mode 100644 index 0000000000..b3ce2449f6 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenpaiOptionsConfigBuilder.java @@ -0,0 +1,86 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class OpenpaiOptionsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + private TcpOptionsConfig tcp; + private TlsOptionsConfig tls; + private HttpOptionsConfig http; + private List openapis; + + OpenpaiOptionsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public OpenpaiOptionsConfigBuilder tcp( + TcpOptionsConfig tcp) + { + this.tcp = tcp; + return this; + } + + public OpenpaiOptionsConfigBuilder tls( + TlsOptionsConfig tls) + { + this.tls = tls; + return this; + } + + public OpenpaiOptionsConfigBuilder http( + HttpOptionsConfig http) + { + this.http = http; + return this; + } + + public OpenpaiOptionsConfigBuilder openapi( + OpenapiConfig openapi) + { + if (openapis == null) + { + openapis = new ArrayList<>(); + } + openapis.add(openapi); + return this; + } + + @Override + public T build() + { + return mapper.apply(new OpenapiOptionsConfig(tcp, tls, http, openapis)); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBinding.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBinding.java new file mode 100644 index 0000000000..8c9a3f9bd1 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBinding.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import java.net.URL; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.Binding; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public final class OpenapiBinding implements Binding +{ + public static final String NAME = "openapi"; + + private final OpenapiConfiguration config; + + OpenapiBinding( + OpenapiConfiguration config) + { + this.config = config; + } + + @Override + public String name() + { + return NAME; + } + + @Override + public URL type() + { + return getClass().getResource("schema/openapi.schema.patch.json"); + } + + @Override + public String originType( + KindConfig kind) + { + return kind == KindConfig.CLIENT ? NAME : null; + } + + @Override + public String routedType( + KindConfig kind) + { + return kind == KindConfig.SERVER ? NAME : null; + } + + @Override + public OpenapiBindingContext supply( + EngineContext context) + { + return new OpenapiBindingContext(config, context); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingContext.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingContext.java new file mode 100644 index 0000000000..048c400107 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingContext.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; + +import java.util.EnumMap; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.openapi.internal.streams.OpenapiClientFactory; +import io.aklivity.zilla.runtime.binding.openapi.internal.streams.OpenapiServerFactory; +import io.aklivity.zilla.runtime.binding.openapi.internal.streams.OpenapiStreamFactory; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +final class OpenapiBindingContext implements BindingContext +{ + private final Map factories; + + OpenapiBindingContext( + OpenapiConfiguration config, + EngineContext context) + { + Map factories = new EnumMap<>(KindConfig.class); + factories.put(SERVER, new OpenapiServerFactory(config, context)); + factories.put(CLIENT, new OpenapiClientFactory(config, context)); + this.factories = factories; + } + + @Override + public BindingHandler attach( + BindingConfig binding) + { + OpenapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.attach(binding); + } + + return factory; + } + + @Override + public void detach( + BindingConfig binding) + { + OpenapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.detach(binding.id); + } + } + + @Override + public String toString() + { + return String.format("%s %s", getClass().getSimpleName(), factories); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingFactorySpi.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingFactorySpi.java new file mode 100644 index 0000000000..d587d85f5a --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingFactorySpi.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import io.aklivity.zilla.runtime.common.feature.Incubating; +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi; + +@Incubating +public final class OpenapiBindingFactorySpi implements BindingFactorySpi +{ + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + @Override + public OpenapiBinding create( + Configuration config) + { + return new OpenapiBinding(new OpenapiConfiguration(config)); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfiguration.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfiguration.java new file mode 100644 index 0000000000..d745d35d2c --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import io.aklivity.zilla.runtime.engine.Configuration; + +public class OpenapiConfiguration extends Configuration +{ + public static final LongPropertyDef OPENAPI_TARGET_ROUTE_ID; + private static final ConfigurationDef OPENAPI_CONFIG; + + static + { + final ConfigurationDef config = new ConfigurationDef("zilla.binding.openapi"); + OPENAPI_TARGET_ROUTE_ID = config.property("target.route.id", -1L); + OPENAPI_CONFIG = config; + } + + public OpenapiConfiguration( + Configuration config) + { + super(OPENAPI_CONFIG, config); + } + + public long targetRouteId() + { + return OPENAPI_TARGET_ROUTE_ID.getAsLong(this); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java new file mode 100644 index 0000000000..58c5fae230 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java @@ -0,0 +1,251 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static java.util.Collections.unmodifiableMap; +import static java.util.stream.Collector.of; +import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.agrona.AsciiSequenceView; +import org.agrona.DirectBuffer; +import org.agrona.collections.IntHashSet; +import org.agrona.collections.Long2LongHashMap; +import org.agrona.collections.Object2ObjectHashMap; + +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.HttpHeaderFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.String16FW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.String8FW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.HttpBeginExFW; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class OpenapiBindingConfig +{ + public final long id; + public final String name; + public final KindConfig kind; + public final OpenapiOptionsConfig options; + public final List routes; + public final HttpHeaderHelper helper; + + private final long overrideRouteId; + private final IntHashSet httpOrigins; + private final Long2LongHashMap resolvedIds; + private final Object2ObjectHashMap paths; + private final Map> resolversByMethod; + + public OpenapiBindingConfig( + BindingConfig binding, + long overrideRouteId) + { + this.id = binding.id; + this.name = binding.name; + this.kind = binding.kind; + this.overrideRouteId = overrideRouteId; + this.options = OpenapiOptionsConfig.class.cast(binding.options); + this.paths = new Object2ObjectHashMap<>(); + options.openapis.forEach(c -> c.openapi.paths.forEach((k, v) -> + { + String regex = k.replaceAll("\\{[^/]+}", "[^/]+"); + regex = "^" + regex + "$"; + Pattern pattern = Pattern.compile(regex); + paths.put(pattern.matcher(""), v); + })); + + this.routes = binding.routes.stream().map(OpenapiRouteConfig::new).collect(toList()); + + this.resolvedIds = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("http")) + .collect(of( + () -> new Long2LongHashMap(-1), + (m, r) -> m.put(0L, r.id), //TODO: populate proper apiId + (m, r) -> m, + IDENTITY_FINISH + )); + + this.httpOrigins = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("http")) + .map(b -> NamespacedId.namespaceId(b.id)) + .collect(toCollection(IntHashSet::new)); + this.helper = new HttpHeaderHelper(); + + Map> resolversByMethod = new TreeMap<>(CharSequence::compare); + resolversByMethod.put("POST", o -> o.post != null ? o.post.operationId : null); + resolversByMethod.put("PUT", o -> o.put != null ? o.put.operationId : null); + resolversByMethod.put("GET", o -> o.get != null ? o.get.operationId : null); + resolversByMethod.put("DELETE", o -> o.delete != null ? o.delete.operationId : null); + resolversByMethod.put("OPTIONS", o -> o.options != null ? o.options.operationId : null); + resolversByMethod.put("HEAD", o -> o.head != null ? o.head.operationId : null); + resolversByMethod.put("PATCH", o -> o.patch != null ? o.patch.operationId : null); + resolversByMethod.put("TRACE", o -> o.post != null ? o.trace.operationId : null); + this.resolversByMethod = unmodifiableMap(resolversByMethod); + } + + public boolean isCompositeNamespace( + int namespaceId) + { + return httpOrigins.contains(namespaceId); + } + + public long resolveResolvedId( + long apiId) + { + return overrideRouteId != -1 ? overrideRouteId : resolvedIds.get(apiId); + } + + public String resolveOperationId( + HttpBeginExFW httpBeginEx) + { + helper.visit(httpBeginEx); + + String operationId = null; + + resolve: + for (Map.Entry item : paths.entrySet()) + { + Matcher matcher = item.getKey(); + matcher.reset(helper.path); + if (matcher.find()) + { + PathItem operations = item.getValue(); + operationId = resolveMethod(operations); + break resolve; + } + } + + return operationId; + } + + private String resolveMethod( + PathItem operations) + { + Function resolver = resolversByMethod.get(helper.method); + return resolver != null ? resolver.apply(operations) : null; + } + + public OpenapiRouteConfig resolve( + long authorization) + { + return routes.stream() + .filter(r -> r.authorized(authorization)) + .findFirst() + .orElse(null); + } + + private static final class HttpHeaderHelper + { + private static final String8FW HEADER_NAME_METHOD = new String8FW(":method"); + private static final String8FW HEADER_NAME_PATH = new String8FW(":path"); + private static final String8FW HEADER_NAME_SCHEME = new String8FW(":scheme"); + private static final String8FW HEADER_NAME_AUTHORITY = new String8FW(":authority"); + + private final Map> visitors; + { + Map> visitors = new HashMap<>(); + visitors.put(HEADER_NAME_METHOD, this::visitMethod); + visitors.put(HEADER_NAME_PATH, this::visitPath); + visitors.put(HEADER_NAME_SCHEME, this::visitScheme); + visitors.put(HEADER_NAME_AUTHORITY, this::visitAuthority); + this.visitors = visitors; + } + private final AsciiSequenceView methodRO = new AsciiSequenceView(); + private final AsciiSequenceView pathRO = new AsciiSequenceView(); + private final String16FW schemeRO = new String16FW(); + private final String16FW authorityRO = new String16FW(); + + public CharSequence path; + public CharSequence method; + public String16FW scheme; + public String16FW authority; + + private void visit( + HttpBeginExFW beginEx) + { + method = null; + path = null; + scheme = null; + authority = null; + + if (beginEx != null) + { + beginEx.headers().forEach(this::dispatch); + } + } + + private boolean dispatch( + HttpHeaderFW header) + { + final String8FW name = header.name(); + final Consumer visitor = visitors.get(name); + if (visitor != null) + { + visitor.accept(header.value()); + } + + return method != null && + path != null && + scheme != null && + authority != null; + } + + private void visitMethod( + String16FW value) + { + final DirectBuffer buffer = value.buffer(); + final int offset = value.offset() + value.fieldSizeLength(); + final int length = value.sizeof() - value.fieldSizeLength(); + method = methodRO.wrap(buffer, offset, length); + } + + private void visitPath( + String16FW value) + { + final DirectBuffer buffer = value.buffer(); + final int offset = value.offset() + value.fieldSizeLength(); + final int length = value.sizeof() - value.fieldSizeLength(); + path = pathRO.wrap(buffer, offset, length); + } + + private void visitScheme( + String16FW value) + { + scheme = schemeRO.wrap(value.buffer(), value.offset(), value.limit()); + } + + private void visitAuthority( + String16FW value) + { + authority = authorityRO.wrap(value.buffer(), value.offset(), value.limit()); + } + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java new file mode 100644 index 0000000000..7d1f47c21b --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java @@ -0,0 +1,255 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static java.util.Objects.requireNonNull; + +import java.net.URI; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.config.HttpResponseConfigBuilder; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiHeader; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiResponse; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiServer; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.ResponseByContentType; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiOperationView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiOperationsView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiPathView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiSchemaView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiServerView; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.ModelConfig; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; +import io.aklivity.zilla.runtime.model.core.config.IntegerModelConfig; +import io.aklivity.zilla.runtime.model.core.config.StringModelConfig; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public final class OpenapiClientCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private static final String INLINE_CATALOG_NAME = "catalog0"; + + private final Map models = Map.of( + "string", StringModelConfig.builder().build(), + "integer", IntegerModelConfig.builder().build() + ); + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + public OpenapiClientCompositeBindingAdapter() + { + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + OpenapiOptionsConfig options = (OpenapiOptionsConfig) binding.options; + OpenapiConfig openapiConfig = options.openapis.get(0); + + final OpenApi openApi = openapiConfig.openapi; + final int[] httpsPorts = resolvePortsForScheme(openApi, "https"); + final boolean secure = httpsPorts != null; + + return BindingConfig.builder(binding) + .composite() + .name(String.format(binding.qname, "$composite")) + .binding() + .name("http_client0") + .type("http") + .kind(CLIENT) + .inject(b -> this.injectHttpClientOptions(b, openApi)) + .exit(secure ? "tls_client0" : "tcp_client0") + .build() + .inject(b -> this.injectTlsClient(b, options.tls, secure)) + .binding() + .name("tcp_client0") + .type("tcp") + .kind(CLIENT) + .options(options.tcp) + .build() + .build() + .build(); + } + + private int[] resolvePortsForScheme( + OpenApi openApi, + String scheme) + { + requireNonNull(scheme); + int[] ports = null; + URI url = findFirstServerUrlWithScheme(openApi, scheme); + if (url != null) + { + ports = new int[] {url.getPort()}; + } + return ports; + } + + private URI findFirstServerUrlWithScheme( + OpenApi openApi, + String scheme) + { + requireNonNull(scheme); + URI result = null; + for (OpenApiServer item : openApi.servers) + { + OpenApiServerView server = OpenApiServerView.of(item); + if (scheme.equals(server.url().getScheme())) + { + result = server.url(); + break; + } + } + return result; + } + + private BindingConfigBuilder injectHttpClientOptions( + BindingConfigBuilder binding, + OpenApi openApi) + { + OpenApiOperationsView operations = OpenApiOperationsView.of(openApi.paths); + if (operations.hasResponses()) + { + binding. + options(HttpOptionsConfig::builder) + .inject(options -> injectHttpClientRequests(operations, options, openApi)) + .build(); + } + return binding; + } + + private HttpOptionsConfigBuilder injectHttpClientRequests( + OpenApiOperationsView operations, + HttpOptionsConfigBuilder options, + OpenApi openApi) + { + for (String pathName : openApi.paths.keySet()) + { + OpenApiPathView path = OpenApiPathView.of(openApi.paths.get(pathName)); + for (String methodName : path.methods().keySet()) + { + OpenApiOperationView operation = operations.operation(pathName, methodName); + if (operation.hasResponses()) + { + options + .request() + .path(pathName) + .method(HttpRequestConfig.Method.valueOf(methodName)) + .inject(request -> injectResponses(request, operation, openApi)) + .build() + .build(); + } + } + } + return options; + } + + private HttpRequestConfigBuilder injectResponses( + HttpRequestConfigBuilder request, + OpenApiOperationView operation, + OpenApi openApi) + { + if (operation != null && operation.responsesByStatus() != null) + { + for (Map.Entry responses0 : operation.responsesByStatus().entrySet()) + { + String status = responses0.getKey(); + ResponseByContentType responses1 = responses0.getValue(); + if (!(OpenApiOperationView.DEFAULT.equals(status)) && responses1.content != null) + { + for (Map.Entry response2 : responses1.content.entrySet()) + { + OpenApiSchemaView schema = OpenApiSchemaView.of(openApi.components.schemas, response2.getValue().schema); + request + .response() + .status(Integer.parseInt(status)) + .contentType(response2.getKey()) + .inject(response -> injectResponseHeaders(responses1, response)) + .content(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .schema() + .subject(schema.refKey()) + .build() + .build() + .build() + .build(); + } + } + } + } + return request; + } + + private HttpResponseConfigBuilder injectResponseHeaders( + ResponseByContentType responses, + HttpResponseConfigBuilder response) + { + if (responses.headers != null && !responses.headers.isEmpty()) + { + for (Map.Entry header : responses.headers.entrySet()) + { + String name = header.getKey(); + ModelConfig model = models.get(header.getValue().schema.type); + if (model != null) + { + response + .header() + .name(name) + .model(model) + .build(); + } + } + } + return response; + } + + private NamespaceConfigBuilder injectTlsClient( + NamespaceConfigBuilder namespace, + TlsOptionsConfig tlsConfig, + boolean secure) + { + if (secure) + { + namespace + .binding() + .name("tls_client0") + .type("tls") + .kind(CLIENT) + .options(tlsConfig) + .vault("client") + .exit("tcp_client0") + .build(); + } + return namespace; + } + +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiCompositeBindingAdapter.java new file mode 100644 index 0000000000..06ad49a40c --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiCompositeBindingAdapter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; +import static java.util.function.UnaryOperator.identity; + +import java.util.EnumMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public final class OpenapiCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private final UnaryOperator composite; + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + public OpenapiCompositeBindingAdapter() + { + Map> composites = new EnumMap<>(KindConfig.class); + composites.put(SERVER, new OpenapiServerCompositeBindingAdapter()::adapt); + composites.put(CLIENT, new OpenapiClientCompositeBindingAdapter()::adapt); + UnaryOperator composite = binding -> composites + .getOrDefault(binding.kind, identity()).apply(binding); + this.composite = composite; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + return composite.apply(binding); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java new file mode 100644 index 0000000000..c967db887f --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java @@ -0,0 +1,267 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static java.util.Collections.unmodifiableMap; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.io.InputStream; +import java.io.StringReader; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonReader; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import org.agrona.collections.Object2ObjectHashMap; +import org.leadpony.justify.api.JsonSchema; +import org.leadpony.justify.api.JsonValidationService; +import org.leadpony.justify.api.ProblemHandler; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenpaiOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.ConfigException; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; + +public final class OpenapiOptionsConfigAdapter implements OptionsConfigAdapterSpi, JsonbAdapter +{ + private static final String TCP_NAME = "tcp"; + private static final String TLS_NAME = "tls"; + private static final String HTTP_NAME = "http"; + private static final String SPECS_NAME = "specs"; + + private final Map schemas; + + private OptionsConfigAdapter tcpOptions; + private OptionsConfigAdapter tlsOptions; + private OptionsConfigAdapter httpOptions; + private Function readURL; + + public OpenapiOptionsConfigAdapter() + { + Map schemas = new Object2ObjectHashMap<>(); + schemas.put("3.0.0", schema("3.0.0")); + schemas.put("3.1.0", schema("3.1.0")); + this.schemas = unmodifiableMap(schemas); + } + + @Override + public Kind kind() + { + return Kind.BINDING; + } + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + OptionsConfig options) + { + OpenapiOptionsConfig openOptions = (OpenapiOptionsConfig) options; + + JsonObjectBuilder object = Json.createObjectBuilder(); + + if (openOptions.tcp != null) + { + final TcpOptionsConfig tcp = ((OpenapiOptionsConfig) options).tcp; + object.add(TCP_NAME, tcpOptions.adaptToJson(tcp)); + } + + if (openOptions.tls != null) + { + final TlsOptionsConfig tls = ((OpenapiOptionsConfig) options).tls; + object.add(TLS_NAME, tlsOptions.adaptToJson(tls)); + } + + HttpOptionsConfig http = openOptions.http; + if (http != null) + { + object.add(HTTP_NAME, httpOptions.adaptToJson(http)); + } + + if (openOptions.openapis != null) + { + JsonArrayBuilder keys = Json.createArrayBuilder(); + openOptions.openapis.forEach(p -> keys.add(p.location)); + object.add(SPECS_NAME, keys); + } + + return object.build(); + } + + @Override + public OptionsConfig adaptFromJson( + JsonObject object) + { + OpenpaiOptionsConfigBuilder openapiOptions = OpenapiOptionsConfig.builder(); + + if (object.containsKey(TCP_NAME)) + { + final JsonObject tcp = object.getJsonObject(TCP_NAME); + final TcpOptionsConfig tcpOptions = (TcpOptionsConfig) this.tcpOptions.adaptFromJson(tcp); + openapiOptions.tcp(tcpOptions); + } + + if (object.containsKey(TLS_NAME)) + { + final JsonObject tls = object.getJsonObject(TLS_NAME); + final TlsOptionsConfig tlsOptions = (TlsOptionsConfig) this.tlsOptions.adaptFromJson(tls); + openapiOptions.tls(tlsOptions); + } + + if (object.containsKey(HTTP_NAME)) + { + JsonObject http = object.getJsonObject(HTTP_NAME); + + final HttpOptionsConfig httpOptions = (HttpOptionsConfig) this.httpOptions.adaptFromJson(http); + openapiOptions.http(httpOptions); + } + + if (object.containsKey(SPECS_NAME)) + { + object.getJsonArray(SPECS_NAME).forEach(s -> openapiOptions.openapi(asOpenapi(s))); + } + + return openapiOptions.build(); + } + + @Override + public void adaptContext( + ConfigAdapterContext context) + { + this.readURL = context::readURL; + this.tcpOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.tcpOptions.adaptType("tcp"); + this.tlsOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.tlsOptions.adaptType("tls"); + this.httpOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.httpOptions.adaptType("http"); + } + + private OpenapiConfig asOpenapi( + JsonValue value) + { + final String location = ((JsonString) value).getString(); + final String specText = readURL.apply(location); + OpenApi openapi = parseOpenApi(specText); + + return new OpenapiConfig(location, openapi); + } + + private OpenApi parseOpenApi( + String openapiText) + { + OpenApi openApi = null; + + List errors = new LinkedList<>(); + + try + { + String openApiVersion = detectOpenApiVersion(openapiText); + + JsonValidationService service = JsonValidationService.newInstance(); + ProblemHandler handler = service.createProblemPrinter(msg -> errors.add(new ConfigException(msg))); + JsonSchema schema = schemas.get(openApiVersion); + + service.createReader(new StringReader(openapiText), schema, handler).read(); + + Jsonb jsonb = JsonbBuilder.create(); + + openApi = jsonb.fromJson(openapiText, OpenApi.class); + } + catch (Exception ex) + { + errors.add(ex); + } + + if (!errors.isEmpty()) + { + Exception ex = errors.remove(0); + errors.forEach(ex::addSuppressed); + rethrowUnchecked(ex); + } + + return openApi; + } + + private String detectOpenApiVersion( + String openapiText) + { + try (JsonReader reader = Json.createReader(new StringReader(openapiText))) + { + JsonObject json = reader.readObject(); + if (json.containsKey("openapi")) + { + return json.getString("openapi"); + } + else + { + throw new IllegalArgumentException("Unable to determine OpenAPI version."); + } + } + catch (Exception e) + { + throw new RuntimeException("Error reading OpenAPI document.", e); + } + } + + private JsonSchema schema( + String version) + { + InputStream schemaInput = null; + boolean detect = true; + + if (version.startsWith("3.0")) + { + schemaInput = OpenapiBinding.class.getResourceAsStream("schema/openapi.3.0.schema.json"); + } + else if (version.startsWith("3.1")) + { + schemaInput = OpenapiBinding.class.getResourceAsStream("schema/openapi.3.1.schema.json"); + detect = false; + } + + JsonValidationService service = JsonValidationService.newInstance(); + + return service.createSchemaReaderFactoryBuilder() + .withSpecVersionDetection(detect) + .build() + .createSchemaReader(schemaInput) + .read(); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiRouteConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiRouteConfig.java new file mode 100644 index 0000000000..82b092dc23 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiRouteConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import java.util.function.LongPredicate; + +import io.aklivity.zilla.runtime.engine.config.RouteConfig; + +public final class OpenapiRouteConfig +{ + public final long id; + + private final LongPredicate authorized; + + public OpenapiRouteConfig( + RouteConfig route) + { + this.id = route.id; + this.authorized = route.authorized; + } + + boolean authorized( + long authorization) + { + return authorized.test(authorization); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java new file mode 100644 index 0000000000..1cc86feda2 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java @@ -0,0 +1,495 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static io.aklivity.zilla.runtime.binding.http.config.HttpPolicyConfig.CROSS_ORIGIN; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; +import static java.util.Objects.requireNonNull; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; + +import org.agrona.collections.Object2ObjectHashMap; + +import io.aklivity.zilla.runtime.binding.http.config.HttpAuthorizationConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpConditionConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfigBuilder; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiMediaType; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiParameter; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiSchema; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiServer; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiPathView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiSchemaView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiServerView; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpConditionConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.catalog.inline.config.InlineOptionsConfig; +import io.aklivity.zilla.runtime.catalog.inline.config.InlineSchemaConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.GuardedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.ModelConfig; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.RouteConfigBuilder; +import io.aklivity.zilla.runtime.model.core.config.IntegerModelConfig; +import io.aklivity.zilla.runtime.model.core.config.StringModelConfig; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public final class OpenapiServerCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private static final String INLINE_CATALOG_NAME = "catalog0"; + private static final String INLINE_CATALOG_TYPE = "inline"; + private static final String VERSION_LATEST = "latest"; + private static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$"); + + private final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher(""); + private final Map models = Map.of( + "string", StringModelConfig.builder().build(), + "integer", IntegerModelConfig.builder().build() + ); + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + OpenapiOptionsConfig options = (OpenapiOptionsConfig) binding.options; + OpenapiConfig openapiConfig = options.openapis.get(0); + + final OpenApi openApi = openapiConfig.openapi; + final TlsOptionsConfig tlsOption = options.tls != null ? options.tls : null; + final HttpOptionsConfig httpOptions = options.http; + final String guardName = httpOptions != null ? httpOptions.authorization.name : null; + final HttpAuthorizationConfig authorization = httpOptions != null ? httpOptions.authorization : null; + + final int[] allPorts = resolveAllPorts(openApi); + final int[] httpPorts = resolvePortsForScheme(openApi, "http"); + final int[] httpsPorts = resolvePortsForScheme(openApi, "https"); + final boolean secure = httpsPorts != null; + final Map securitySchemes = resolveSecuritySchemes(openApi); + final boolean hasJwt = !securitySchemes.isEmpty(); + + return BindingConfig.builder(binding) + .composite() + .name(String.format("%s/http", binding.qname)) + .inject(n -> this.injectCatalog(n, openApi)) + .binding() + .name("tcp_server0") + .type("tcp") + .kind(SERVER) + .options(TcpOptionsConfig::builder) + .host("0.0.0.0") + .ports(allPorts) + .build() + .inject(b -> this.injectPlainTcpRoute(b, httpPorts, secure)) + .inject(b -> this.injectTlsTcpRoute(b, httpsPorts, secure)) + .build() + .inject(n -> this.injectTlsServer(n, tlsOption, secure)) + .binding() + .name("http_server0") + .type("http") + .kind(SERVER) + .options(HttpOptionsConfig::builder) + .access() + .policy(CROSS_ORIGIN) + .build() + .inject(o -> this.injectHttpServerOptions(o, authorization, hasJwt)) + .inject(r -> this.injectHttpServerRequests(r, openApi)) + .build() + .inject(b -> this.injectHttpServerRoutes(b, openApi, binding.qname, guardName, securitySchemes)) + .build() + .build() + .build(); + } + + private BindingConfigBuilder injectPlainTcpRoute( + BindingConfigBuilder binding, + int[] httpPorts, + boolean secure) + { + if (secure) + { + binding + .route() + .when(TcpConditionConfig::builder) + .ports(httpPorts) + .build() + .exit("http_server0") + .build(); + } + return binding; + } + + private BindingConfigBuilder injectTlsTcpRoute( + BindingConfigBuilder binding, + int[] httpsPorts, + boolean secure) + { + if (secure) + { + binding + .route() + .when(TcpConditionConfig::builder) + .ports(httpsPorts) + .build() + .exit("tls_server0") + .build(); + } + return binding; + } + + private NamespaceConfigBuilder injectTlsServer( + NamespaceConfigBuilder namespace, + TlsOptionsConfig tls, + boolean secure) + { + if (secure) + { + namespace + .binding() + .name("tls_server0") + .type("tls") + .kind(SERVER) + .options(tls) + .vault("server") + .exit("http_server0") + .build(); + } + return namespace; + } + + private HttpOptionsConfigBuilder injectHttpServerOptions( + HttpOptionsConfigBuilder options, + HttpAuthorizationConfig authorization, + boolean hasJwt) + { + if (hasJwt) + { + options.authorization(authorization).build(); + } + return options; + } + + private HttpOptionsConfigBuilder injectHttpServerRequests( + HttpOptionsConfigBuilder options, + OpenApi openApi) + { + for (String pathName : openApi.paths.keySet()) + { + OpenApiPathView path = OpenApiPathView.of(openApi.paths.get(pathName)); + for (String methodName : path.methods().keySet()) + { + OpenApiOperation operation = path.methods().get(methodName); + if (operation.requestBody != null || operation.parameters != null && !operation.parameters.isEmpty()) + { + options + .request() + .path(pathName) + .method(HttpRequestConfig.Method.valueOf(methodName)) + .inject(request -> injectContent(request, operation, openApi)) + .inject(request -> injectParams(request, operation)) + .build(); + } + } + } + return options; + } + + private HttpRequestConfigBuilder injectContent( + HttpRequestConfigBuilder request, + OpenApiOperation operation, + OpenApi openApi) + { + if (operation.requestBody != null && operation.requestBody.content != null && !operation.requestBody.content.isEmpty()) + { + OpenApiSchemaView schema = resolveSchemaForJsonContentType(operation.requestBody.content, openApi); + if (schema != null) + { + request. + content(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .schema() + .subject(schema.refKey()) + .build() + .build() + .build(); + } + } + return request; + } + + private HttpRequestConfigBuilder injectParams( + HttpRequestConfigBuilder request, + OpenApiOperation operation) + { + if (operation != null && operation.parameters != null) + { + for (OpenApiParameter parameter : operation.parameters) + { + if (parameter.schema != null && parameter.schema.type != null) + { + ModelConfig model = models.get(parameter.schema.type); + if (model != null) + { + switch (parameter.in) + { + case "path": + request. + pathParam() + .name(parameter.name) + .model(model) + .build(); + break; + case "query": + request. + queryParam() + .name(parameter.name) + .model(model) + .build(); + break; + case "header": + request. + header() + .name(parameter.name) + .model(model) + .build(); + break; + } + } + } + } + } + return request; + } + + private BindingConfigBuilder injectHttpServerRoutes( + BindingConfigBuilder binding, + OpenApi openApi, + String qname, + String guardName, + Map securitySchemes) + { + for (String item : openApi.paths.keySet()) + { + OpenApiPathView path = OpenApiPathView.of(openApi.paths.get(item)); + for (String method : path.methods().keySet()) + { + binding + .route() + .exit(qname) + .when(HttpConditionConfig::builder) + .header(":path", item.replaceAll("\\{[^}]+\\}", "*")) + .header(":method", method) + .build() + .inject(route -> injectHttpServerRouteGuarded(route, path, method, guardName, securitySchemes)) + .build(); + } + } + return binding; + } + + private RouteConfigBuilder injectHttpServerRouteGuarded( + RouteConfigBuilder route, + OpenApiPathView path, + String method, + String guardName, + Map securitySchemes) + { + final List>> security = path.methods().get(method).security; + final boolean hasJwt = securitySchemes.isEmpty(); + + if (security != null) + { + for (Map> securityItem : security) + { + for (String securityItemLabel : securityItem.keySet()) + { + if (hasJwt && "jwt".equals(securitySchemes.get(securityItemLabel))) + { + route + .guarded() + .name(guardName) + .inject(guarded -> injectGuardedRoles(guarded, securityItem.get(securityItemLabel))) + .build(); + } + } + } + } + return route; + } + + private GuardedConfigBuilder injectGuardedRoles( + GuardedConfigBuilder guarded, + List roles) + { + for (String role : roles) + { + guarded.role(role); + } + return guarded; + } + + private NamespaceConfigBuilder injectCatalog( + NamespaceConfigBuilder namespace, + OpenApi openApi) + { + if (openApi.components != null && + openApi.components.schemas != null && + !openApi.components.schemas.isEmpty()) + { + namespace + .catalog() + .name(INLINE_CATALOG_NAME) + .type(INLINE_CATALOG_TYPE) + .options(InlineOptionsConfig::builder) + .subjects() + .inject(s -> this.injectSubjects(s, openApi)) + .build() + .build() + .build(); + } + return namespace; + } + + private InlineSchemaConfigBuilder injectSubjects( + InlineSchemaConfigBuilder subjects, + OpenApi openApi) + { + try (Jsonb jsonb = JsonbBuilder.create()) + { + for (Map.Entry entry : openApi.components.schemas.entrySet()) + { + subjects + .subject(entry.getKey()) + .version(VERSION_LATEST) + .schema(jsonb.toJson(openApi.components.schemas)) + .build(); + } + } + catch (Exception ex) + { + rethrowUnchecked(ex); + } + return subjects; + } + + private int[] resolveAllPorts( + OpenApi openApi) + { + int[] ports = new int[openApi.servers.size()]; + for (int i = 0; i < openApi.servers.size(); i++) + { + OpenApiServerView server = OpenApiServerView.of(openApi.servers.get(i)); + URI url = server.url(); + ports[i] = url.getPort(); + } + return ports; + } + + private int[] resolvePortsForScheme( + OpenApi openApi, + String scheme) + { + requireNonNull(scheme); + int[] ports = null; + URI url = findFirstServerUrlWithScheme(openApi, scheme); + if (url != null) + { + ports = new int[] {url.getPort()}; + } + return ports; + } + + private URI findFirstServerUrlWithScheme( + OpenApi openApi, + String scheme) + { + requireNonNull(scheme); + URI result = null; + for (OpenApiServer item : openApi.servers) + { + OpenApiServerView server = OpenApiServerView.of(item); + if (scheme.equals(server.url().getScheme())) + { + result = server.url(); + break; + } + } + return result; + } + + private Map resolveSecuritySchemes( + OpenApi openApi) + { + requireNonNull(openApi); + Map result = new Object2ObjectHashMap<>(); + if (openApi.components != null && + openApi.components.securitySchemes != null) + { + for (String securitySchemeName : openApi.components.securitySchemes.keySet()) + { + String guardType = openApi.components.securitySchemes.get(securitySchemeName).bearerFormat; + if ("jwt".equals(guardType)) + { + result.put(securitySchemeName, guardType); + } + } + } + return result; + } + + private OpenApiSchemaView resolveSchemaForJsonContentType( + Map content, + OpenApi openApi) + { + OpenApiMediaType mediaType = null; + if (content != null) + { + for (String contentType : content.keySet()) + { + if (jsonContentType.reset(contentType).matches()) + { + mediaType = content.get(contentType); + break; + } + } + } + + return mediaType == null ? null : OpenApiSchemaView.of(openApi.components.schemas, mediaType.schema); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApi.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApi.java new file mode 100644 index 0000000000..d6c6404a31 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApi.java @@ -0,0 +1,26 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.List; +import java.util.Map; + +public class OpenApi +{ + public String openapi; + public List servers; + public Map paths; + public OpenApiComponents components; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiBearerAuth.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiBearerAuth.java new file mode 100644 index 0000000000..db34f20fff --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiBearerAuth.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiBearerAuth +{ + public String bearerFormat; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiComponents.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiComponents.java new file mode 100644 index 0000000000..1f31bb5592 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiComponents.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.Map; + +public class OpenApiComponents +{ + public Map securitySchemes; + public Map schemas; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiHeader.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiHeader.java new file mode 100644 index 0000000000..f91603be21 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiHeader.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiHeader +{ + public OpenApiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiItem.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiItem.java new file mode 100644 index 0000000000..b7ff06d8e3 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiItem.java @@ -0,0 +1,21 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiItem +{ + public String type; + public String description; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiMediaType.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiMediaType.java new file mode 100644 index 0000000000..67635b7d26 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiMediaType.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiMediaType +{ + public OpenApiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java new file mode 100644 index 0000000000..fe9aca2858 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.List; +import java.util.Map; + +public class OpenApiOperation +{ + public List>> security; + public String operationId; + public OpenApiRequestBody requestBody; + public List parameters; + public Map responses; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiParameter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiParameter.java new file mode 100644 index 0000000000..fb713f91d3 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiParameter.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiParameter +{ + public String name; + public String in; + public boolean required; + public OpenApiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiRequestBody.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiRequestBody.java new file mode 100644 index 0000000000..245899b8ee --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiRequestBody.java @@ -0,0 +1,22 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.Map; + +public class OpenApiRequestBody +{ + public Map content; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiResponse.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiResponse.java new file mode 100644 index 0000000000..3f23331a88 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiResponse.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiResponse +{ + public OpenApiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSchema.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSchema.java new file mode 100644 index 0000000000..855472a883 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSchema.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.List; +import java.util.Map; + +import jakarta.json.bind.annotation.JsonbProperty; + +public class OpenApiSchema +{ + public String type; + public OpenApiSchema items; + public Map properties; + public List required; + + @JsonbProperty("$ref") + public String ref; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSecurityScheme.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSecurityScheme.java new file mode 100644 index 0000000000..4d27f750ea --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSecurityScheme.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiSecurityScheme +{ + public String bearerFormat; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiServer.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiServer.java new file mode 100644 index 0000000000..da292428f3 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiServer.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiServer +{ + public String url; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/PathItem.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/PathItem.java new file mode 100644 index 0000000000..64a976faa4 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/PathItem.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class PathItem +{ + public OpenApiOperation get; + public OpenApiOperation put; + public OpenApiOperation post; + public OpenApiOperation delete; + public OpenApiOperation options; + public OpenApiOperation head; + public OpenApiOperation patch; + public OpenApiOperation trace; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/ResponseByContentType.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/ResponseByContentType.java new file mode 100644 index 0000000000..280e28eaee --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/ResponseByContentType.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.Map; + +public class ResponseByContentType +{ + public Map headers; + public Map content; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientFactory.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientFactory.java new file mode 100644 index 0000000000..658a7a5e84 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientFactory.java @@ -0,0 +1,1066 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfiguration; +import io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiBindingConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.OpenapiBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public final class OpenapiClientFactory implements OpenapiStreamFactory +{ + private static final String HTTP_TYPE_NAME = "http"; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final OpenapiBeginExFW openapiBeginExRO = new OpenapiBeginExFW(); + + private final OpenapiBeginExFW.Builder openBeginExRW = new OpenapiBeginExFW.Builder(); + + private final OpenapiConfiguration config; + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Long2ObjectHashMap bindings; + private final int openapiTypeId; + private final int httpTypeId; + + + public OpenapiClientFactory( + OpenapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.openapiTypeId = context.supplyTypeId(OpenapiBinding.NAME); + this.httpTypeId = context.supplyTypeId(HTTP_TYPE_NAME); + } + + @Override + public int originTypeId() + { + return httpTypeId; + } + + @Override + public int routedTypeId() + { + return openapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + OpenapiBindingConfig openapiBinding = new OpenapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, openapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + + final OpenapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null) + { + final long apiId = openapiBeginEx.apiId(); + final String operationId = openapiBeginEx.operationId().asString(); + + final long resolvedId = binding.resolveResolvedId(apiId); + + if (resolvedId != -1) + { + newStream = new OpenapiStream( + receiver, + originId, + routedId, + initialId, + affinity, + authorization, + resolvedId, + operationId)::onOpenapiMessage; + } + + } + + return newStream; + } + + private final class OpenapiStream + { + private final HttpStream http; + private final MessageConsumer sender; + private final String operationId; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long replyBudgetId; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + private int replyCap; + + private OpenapiStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long resolvedId, + String operationId) + { + this.http = new HttpStream(this, routedId, resolvedId, authorization); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + this.operationId = operationId; + } + + private void onOpenapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onOpenapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onOpenapiData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onOpenapiEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onOpenapiFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onOpenapiAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onOpenapiWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onOpenapiReset(reset); + break; + default: + break; + } + } + + private void onOpenapiBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = OpenapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + http.doHttpBegin(traceId, openapiBeginEx.extension()); + } + + private void onOpenapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + http.doHttpData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onOpenapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + http.doHttpEnd(traceId, extension); + } + + private void onOpenapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + http.doHttpFlush(traceId, reserved, extension); + } + + private void onOpenapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + http.doHttpAbort(traceId, extension); + } + + private void onOpenapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = OpenapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onOpenapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyBud = budgetId; + replyPad = padding; + replyCap = capabilities; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + http.doHttpWindow(traceId, acknowledge, budgetId, padding); + } + + private void doOpenapiBegin( + long traceId, + OctetsFW extension) + { + state = OpenapiState.openingReply(state); + + final OpenapiBeginExFW openBeginEx = openBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(openapiTypeId) + .operationId(operationId) + .extension(extension) + .build(); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, openBeginEx); + } + + private void doOpenapiData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doOpenapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, reserved, extension); + + replySeq += reserved; + } + + private void doOpenapiEnd( + long traceId, + OctetsFW extension) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = OpenapiState.closeReply(state); + } + + private void doOpenapiAbort( + long traceId) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = OpenapiState.closeInitial(state); + } + + private void doOpenapiReset( + long traceId) + { + if (!OpenapiState.initialClosed(state)) + { + state = OpenapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doOpenapiWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = http.initialAck; + initialMax = http.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doOpenapiReset(traceId); + doOpenapiAbort(traceId); + + http.cleanup(traceId); + } + } + + private final class HttpStream + { + private final OpenapiStream delegate; + private final long originId; + private final long routedId; + private final long authorization; + + private final long initialId; + private final long replyId; + + private MessageConsumer receiver; + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + + private HttpStream( + OpenapiStream delegate, + long originId, + long routedId, + long authorization) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.receiver = MessageConsumer.NOOP; + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.authorization = authorization; + } + + private void onHttpMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onHttpBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onHttpData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onHttpFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onHttpEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onHttpAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onHttpReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onHttpWindow(window); + break; + default: + break; + } + } + + private void onHttpBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = OpenapiState.openingReply(state); + + delegate.doOpenapiBegin(traceId, extension); + } + + private void onHttpData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doOpenapiData(traceId, flags, reserved, payload, extension); + } + + private void onHttpFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doOpenapiFlush(traceId, reserved, extension); + } + + private void onHttpEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doOpenapiEnd(traceId, extension); + } + + private void onHttpAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doOpenapiAbort(traceId); + } + + private void onHttpReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = OpenapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doOpenapiReset(traceId); + } + + private void onHttpWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = OpenapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + delegate.doOpenapiWindow(authorization, traceId, budgetId, padding); + } + + private void doHttpBegin( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialOpening(state)) + { + assert state == 0; + + this.receiver = newStream(this::onHttpMessage, originId, routedId, initialId, initialSeq, + initialAck, initialMax, traceId, authorization, 0L, extension); + state = OpenapiState.openingInitial(state); + } + } + + private void doHttpData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doHttpFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doHttpEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doHttpAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doHttpReset( + long traceId) + { + if (!OpenapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = OpenapiState.closeReply(state); + } + } + + private void doHttpWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - padding, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doHttpAbort(traceId, EMPTY_OCTETS); + doHttpReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java new file mode 100644 index 0000000000..95b9ae0233 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java @@ -0,0 +1,1070 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfiguration; +import io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiBindingConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiRouteConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.HttpBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.OpenapiBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class OpenapiServerFactory implements OpenapiStreamFactory +{ + private static final String HTTP_TYPE_NAME = "http"; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final OpenapiBeginExFW openapiBeginExRO = new OpenapiBeginExFW(); + private final HttpBeginExFW httpBeginExRO = new HttpBeginExFW(); + + private final OpenapiBeginExFW.Builder openapiBeginExRW = new OpenapiBeginExFW.Builder(); + + private final OpenapiConfiguration config; + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Long2ObjectHashMap bindings; + private final int openapiTypeId; + private final int httpTypeId; + + public OpenapiServerFactory( + OpenapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.openapiTypeId = context.supplyTypeId(OpenapiBinding.NAME); + this.httpTypeId = context.supplyTypeId(HTTP_TYPE_NAME); + } + + @Override + public int originTypeId() + { + return httpTypeId; + } + + @Override + public int routedTypeId() + { + return openapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + OpenapiBindingConfig openapiBinding = new OpenapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, openapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + final HttpBeginExFW httpBeginEx = extension.get(httpBeginExRO::tryWrap); + + final OpenapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null && binding.isCompositeNamespace(NamespacedId.namespaceId(originId))) + { + + final OpenapiRouteConfig route = binding.resolve(authorization); + + if (route != null) + { + final String operationId = binding.resolveOperationId(httpBeginEx); + + newStream = new HttpStream( + receiver, + originId, + routedId, + initialId, + affinity, + authorization, + route.id, + operationId)::onHttpMessage; + } + + } + + return newStream; + } + + private final class HttpStream + { + private final OpenapiStream openapi; + private final MessageConsumer sender; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long replyBudgetId; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private HttpStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long resolvedId, + String operationId) + { + this.openapi = new OpenapiStream(this, routedId, resolvedId, authorization, operationId); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + } + + private void onHttpMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onHttpBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onHttpData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onHttpEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onHttpFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onHttpAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onHttpWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onHttpReset(reset); + break; + default: + break; + } + } + + private void onHttpBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final long authorization = begin.authorization(); + final long affinity = begin.affinity(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = OpenapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + openapi.doOpenapiBegin(traceId, extension); + } + + private void onHttpData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + openapi.doOpenapiData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onHttpEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + openapi.doOpenapiEnd(traceId, extension); + } + + private void onHttpFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + openapi.doOpenapiFlush(traceId, reserved, extension); + } + + private void onHttpAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + openapi.doOpenapiAbort(traceId, extension); + } + + private void onHttpReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = OpenapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onHttpWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyPad = padding; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + openapi.doOpenapiWindow(traceId, acknowledge, budgetId, padding); + } + + private void doHttpBegin( + long traceId, + OctetsFW extension) + { + state = OpenapiState.openingReply(state); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, extension); + } + + private void doHttpData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doHttpFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, reserved, extension); + + replySeq += reserved; + } + + private void doHttpEnd( + long traceId, + OctetsFW extension) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = OpenapiState.closeReply(state); + } + + private void doHttpAbort( + long traceId) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = OpenapiState.closeInitial(state); + } + + private void doHttpReset( + long traceId) + { + if (!OpenapiState.initialClosed(state)) + { + state = OpenapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doHttpWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = openapi.initialAck; + initialMax = openapi.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doHttpReset(traceId); + doHttpAbort(traceId); + + openapi.cleanup(traceId); + } + } + + private final class OpenapiStream + { + private final HttpStream delegate; + private final String operationId; + private final long originId; + private final long routedId; + private final long authorization; + + private final long initialId; + private final long replyId; + private MessageConsumer receiver; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private OpenapiStream( + HttpStream delegate, + long originId, + long routedId, + long authorization, + String operationId) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.receiver = MessageConsumer.NOOP; + this.authorization = authorization; + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.operationId = operationId; + } + + private void onOpenapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onOpenapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onOpenapiData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onOpenapiFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onOpenapiEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onOpenapiAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onOpenapiReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onOpenapiWindow(window); + break; + default: + break; + } + } + + private void onOpenapiBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = OpenapiState.openingReply(state); + + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + + delegate.doHttpBegin(traceId, openapiBeginEx.extension()); + } + + private void onOpenapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doHttpData(traceId, flags, reserved, payload, extension); + } + + private void onOpenapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doHttpFlush(traceId, reserved, extension); + } + + private void onOpenapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doHttpEnd(traceId, extension); + } + + private void onOpenapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doHttpAbort(traceId); + } + + private void onOpenapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = OpenapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doHttpReset(traceId); + } + + + private void onOpenapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = OpenapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doHttpWindow(authorization, traceId, budgetId, padding); + } + + private void doOpenapiBegin( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialOpening(state)) + { + assert state == 0; + + final OpenapiBeginExFW openapiBeginEx = openapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(openapiTypeId) + .operationId(operationId) + .extension(extension) + .build(); + + this.receiver = newStream(this::onOpenapiMessage, originId, routedId, initialId, initialSeq, + initialAck, initialMax, traceId, authorization, 0L, openapiBeginEx); + state = OpenapiState.openingInitial(state); + } + } + + private void doOpenapiData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doOpenapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doOpenapiEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doOpenapiAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doOpenapiReset( + long traceId) + { + if (!OpenapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = OpenapiState.closeReply(state); + } + } + + private void doOpenapiWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void cleanup( + long traceId) + { + doOpenapiAbort(traceId, EMPTY_OCTETS); + doOpenapiReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiState.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiState.java new file mode 100644 index 0000000000..687aae313f --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiState.java @@ -0,0 +1,134 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +public final class OpenapiState +{ + private static final int INITIAL_OPENING = 0x10; + private static final int INITIAL_OPENED = 0x20; + private static final int INITIAL_CLOSING = 0x40; + private static final int INITIAL_CLOSED = 0x80; + private static final int REPLY_OPENING = 0x01; + private static final int REPLY_OPENED = 0x02; + private static final int REPLY_CLOSING = 0x04; + private static final int REPLY_CLOSED = 0x08; + + static int openingInitial( + int state) + { + return state | INITIAL_OPENING; + } + + static int openInitial( + int state) + { + return openingInitial(state) | INITIAL_OPENED; + } + + static int closingInitial( + int state) + { + return state | INITIAL_CLOSING; + } + + static int closeInitial( + int state) + { + return closingInitial(state) | INITIAL_CLOSED; + } + + static boolean initialOpening( + int state) + { + return (state & INITIAL_OPENING) != 0; + } + + static boolean initialOpened( + int state) + { + return (state & INITIAL_OPENED) != 0; + } + + static boolean initialClosing( + int state) + { + return (state & INITIAL_CLOSING) != 0; + } + + static boolean initialClosed( + int state) + { + return (state & INITIAL_CLOSED) != 0; + } + + static boolean closed( + int state) + { + return initialClosed(state) && replyClosed(state); + } + + static int openingReply( + int state) + { + return state | REPLY_OPENING; + } + + static int openReply( + int state) + { + return state | REPLY_OPENED; + } + + static boolean replyOpening( + int state) + { + return (state & REPLY_OPENING) != 0; + } + + static boolean replyOpened( + int state) + { + return (state & REPLY_OPENED) != 0; + } + + static int closingReply( + int state) + { + return state | REPLY_CLOSING; + } + + static boolean replyClosing( + int state) + { + return (state & REPLY_CLOSING) != 0; + } + + static int closeReply( + int state) + { + return closingReply(state) | REPLY_CLOSED; + } + + static boolean replyClosed( + int state) + { + return (state & REPLY_CLOSED) != 0; + } + + private OpenapiState() + { + // utility + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiStreamFactory.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiStreamFactory.java new file mode 100644 index 0000000000..706267b5ef --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiStreamFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public interface OpenapiStreamFactory extends BindingHandler +{ + void attach( + BindingConfig binding); + + void detach( + long bindingId); +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java new file mode 100644 index 0000000000..4a5baa320b --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.ResponseByContentType; + +public final class OpenApiOperationView +{ + public static final String DEFAULT = "default"; + + private final OpenApiOperation operation; + private final boolean hasResponses; + + private OpenApiOperationView( + OpenApiOperation operation) + { + this.operation = operation; + this.hasResponses = initHasResponses(); + } + + public Map responsesByStatus() + { + return operation.responses; + } + + public boolean hasResponses() + { + return hasResponses; + } + + private boolean initHasResponses() + { + boolean result = false; + if (operation != null && operation.responses != null) + { + for (Map.Entry response0 : operation.responses.entrySet()) + { + String status = response0.getKey(); + ResponseByContentType response1 = response0.getValue(); + if (!(DEFAULT.equals(status)) && response1.content != null) + { + result = true; + break; + } + } + } + return result; + } + + public static OpenApiOperationView of( + OpenApiOperation operation) + { + return new OpenApiOperationView(operation); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java new file mode 100644 index 0000000000..63265476d5 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.agrona.collections.Object2ObjectHashMap; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; + +public final class OpenApiOperationsView +{ + private final Map> operationsByPath; + private final boolean hasResponses; + + public boolean hasResponses() + { + return this.hasResponses; + } + + public OpenApiOperationView operation( + String pathName, + String methodName) + { + return operationsByPath.get(pathName).get(methodName); + } + + public static OpenApiOperationsView of( + Map paths) + { + return new OpenApiOperationsView(paths); + } + + private OpenApiOperationsView( + Map paths) + { + this.operationsByPath = new Object2ObjectHashMap<>(); + boolean hasResponses = false; + for (String pathName : paths.keySet()) + { + OpenApiPathView path = OpenApiPathView.of(paths.get(pathName)); + for (String methodName : path.methods().keySet()) + { + OpenApiOperationView operation = OpenApiOperationView.of(path.methods().get(methodName)); + hasResponses |= operation.hasResponses(); + if (operationsByPath.containsKey(pathName)) + { + operationsByPath.get(pathName).put(methodName, operation); + } + else + { + Map operationsPerMethod = new LinkedHashMap<>(); + operationsPerMethod.put(methodName, operation); + operationsByPath.put(pathName, operationsPerMethod); + } + } + } + this.hasResponses = hasResponses; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java new file mode 100644 index 0000000000..4e80c12958 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import static java.util.Collections.unmodifiableMap; + +import java.util.LinkedHashMap; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; + +public final class OpenApiPathView +{ + private final Map methods; + + public Map methods() + { + return methods; + } + + public static OpenApiPathView of( + PathItem pathItem) + { + return new OpenApiPathView(pathItem); + } + + private OpenApiPathView( + PathItem pathItem) + { + Map methods = new LinkedHashMap<>(); + putIfNotNull(methods, "GET", pathItem.get); + putIfNotNull(methods, "PUT", pathItem.put); + putIfNotNull(methods, "POST", pathItem.post); + putIfNotNull(methods, "DELETE", pathItem.delete); + putIfNotNull(methods, "OPTIONS", pathItem.options); + putIfNotNull(methods, "HEAD", pathItem.head); + putIfNotNull(methods, "PATCH", pathItem.patch); + putIfNotNull(methods, "TRACE", pathItem.trace); + this.methods = unmodifiableMap(methods); + } + + private static void putIfNotNull( + Map methods, + String method, + OpenApiOperation operation) + { + if (operation != null) + { + methods.put(method, operation); + } + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiResolvable.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiResolvable.java new file mode 100644 index 0000000000..fc5babcde0 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiResolvable.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class OpenApiResolvable +{ + private final Map map; + private final Matcher matcher; + + protected String key; + + public OpenApiResolvable( + Map map, + String regex) + { + this.map = map; + this.matcher = Pattern.compile(regex).matcher(""); + } + + protected T resolveRef( + String ref) + { + T result = null; + if (matcher.reset(ref).matches()) + { + key = matcher.group(1); + result = map.get(key); + } + return result; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiSchemaView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiSchemaView.java new file mode 100644 index 0000000000..29eddefb26 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiSchemaView.java @@ -0,0 +1,86 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.List; +import java.util.Map; + +import jakarta.json.bind.annotation.JsonbPropertyOrder; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiItem; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiSchema; + +@JsonbPropertyOrder({ + "type", + "items", + "properties", + "required" +}) +public final class OpenApiSchemaView extends OpenApiResolvable +{ + private static final String ARRAY_TYPE = "array"; + + private final OpenApiSchema schema; + private final Map schemas; + + private OpenApiSchemaView( + Map schemas, + OpenApiSchema schema) + { + super(schemas, "#/components/schemas/(\\w+)"); + if (schema.ref != null) + { + schema = resolveRef(schema.ref); + } + else if (ARRAY_TYPE.equals(schema.type) && schema.items != null && schema.items.ref != null) + { + schema.items = resolveRef(schema.items.ref); + } + this.schemas = schemas; + this.schema = schema; + } + + public String refKey() + { + return key; + } + + public String getType() + { + return schema.type; + } + + public OpenApiSchemaView getItems() + { + return schema.items == null ? null : OpenApiSchemaView.of(schemas, schema.items); + } + + public Map getProperties() + { + return schema.properties; + } + + public List getRequired() + { + return schema.required; + } + + public static OpenApiSchemaView of( + Map schemas, + OpenApiSchema schema) + { + return new OpenApiSchemaView(schemas, schema); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiServerView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiServerView.java new file mode 100644 index 0000000000..2a78362d66 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiServerView.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.net.URI; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiServer; + +public final class OpenApiServerView +{ + private URI url; + + private OpenApiServerView( + OpenApiServer server) + { + this.url = URI.create(server.url); + } + + public URI url() + { + return url; + } + + public static OpenApiServerView of( + OpenApiServer server) + { + return new OpenApiServerView(server); + } +} diff --git a/incubator/binding-openapi/src/main/moditect/module-info.java b/incubator/binding-openapi/src/main/moditect/module-info.java new file mode 100644 index 0000000000..3a3f23238d --- /dev/null +++ b/incubator/binding-openapi/src/main/moditect/module-info.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +module io.aklivity.zilla.runtime.binding.openapi +{ + requires io.aklivity.zilla.runtime.engine; + requires io.aklivity.zilla.runtime.binding.http; + requires io.aklivity.zilla.runtime.binding.tcp; + requires io.aklivity.zilla.runtime.binding.tls; + requires io.aklivity.zilla.runtime.catalog.inline; + requires io.aklivity.zilla.runtime.guard.jwt; + requires io.aklivity.zilla.runtime.vault.filesystem; + requires io.aklivity.zilla.runtime.model.core; + requires io.aklivity.zilla.runtime.model.json; + + exports io.aklivity.zilla.runtime.binding.openapi.config; + + opens io.aklivity.zilla.runtime.binding.openapi.internal.model; + + provides io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi + with io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBindingFactorySpi; + + provides io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiCompositeBindingAdapter; + + provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiOptionsConfigAdapter; +} diff --git a/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi new file mode 100644 index 0000000000..fa663f0820 --- /dev/null +++ b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBindingFactorySpi diff --git a/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi new file mode 100644 index 0000000000..499d92471c --- /dev/null +++ b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiCompositeBindingAdapter diff --git a/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi new file mode 100644 index 0000000000..52d34a108e --- /dev/null +++ b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiOptionsConfigAdapter diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfigurationTest.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfigurationTest.java new file mode 100644 index 0000000000..1ea1d03090 --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfigurationTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import static io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfiguration.OPENAPI_TARGET_ROUTE_ID; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class OpenapiConfigurationTest +{ + public static final String OPENAPI_TARGET_ROUTE_ID_NAME = "zilla.binding.openapi.target.route.id"; + + @Test + public void shouldVerifyConstants() throws Exception + { + assertEquals(OPENAPI_TARGET_ROUTE_ID.name(), OPENAPI_TARGET_ROUTE_ID_NAME); + } +} diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java new file mode 100644 index 0000000000..5acbad38d6 --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static java.util.function.Function.identity; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.InputStream; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; +import io.aklivity.zilla.specs.binding.openapi.OpenapiSpecs; + +public class OpenapiOptionsConfigAdapterTest +{ + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock + private ConfigAdapterContext context; + + private Jsonb jsonb; + + @Before + public void initJson() throws IOException + { + try (InputStream resource = OpenapiSpecs.class.getResourceAsStream("config/openapi/petstore.yaml")) + { + String content = new String(resource.readAllBytes(), UTF_8); + Mockito.doReturn(content).when(context).readURL("openapi/petstore.yaml"); + OptionsConfigAdapter adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter.adaptType("openapi"); + JsonbConfig config = new JsonbConfig() + .withAdapters(adapter); + jsonb = JsonbBuilder.create(config); + } + } + + @Test + public void shouldReadOptions() + { + String text = + "{" + + " \"tls\": {" + + " \"keys\": [" + + " \"localhost\"" + + " ]," + + " \"alpn\": [" + + " \"localhost\"" + + " ]" + + " }," + + " \"http\": {" + + " \"authorization\": {" + + " \"test0\": {" + + " \"credentials\": {" + + " \"cookies\": {" + + " \"access_token\": \"{credentials}\"" + + " }," + + " \"headers\": {" + + " \"authorization\": \"Bearer {credentials}\"" + + " }," + + " \"query\": {" + + " \"access_token\": \"{credentials}\"" + + " }" + + " }" + + " }" + + " }" + + " }," + + " \"specs\": [" + + " \"openapi/petstore.yaml\"" + + " ]" + + " }"; + + OpenapiOptionsConfig options = jsonb.fromJson(text, OpenapiOptionsConfig.class); + OpenapiConfig openapi = options.openapis.stream().findFirst().get(); + PathItem path = openapi.openapi.paths.get("/pets"); + + assertThat(options, not(nullValue())); + assertThat(path.post, not(nullValue())); + assertThat(options.tls, not(nullValue())); + assertThat(options.http, not(nullValue())); + } + + @Test + public void shouldWriteOptions() + { + String expected = "{\"tcp\":{\"host\":\"localhost\",\"port\":8080},\"tls\":{\"sni\":[\"example.net\"]}," + + "\"specs\":[\"openapi/petstore.yaml\"]}"; + + TcpOptionsConfig tcp = TcpOptionsConfig.builder() + .inject(identity()) + .host("localhost") + .ports(new int[] { 8080 }) + .build(); + + TlsOptionsConfig tls = TlsOptionsConfig.builder() + .inject(identity()) + .sni(asList("example.net")) + .build(); + + OpenapiOptionsConfig options = new OpenapiOptionsConfig(tcp, tls, null, asList( + new OpenapiConfig("openapi/petstore.yaml", new OpenApi()))); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertEquals(expected, text); + } +} diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientIT.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientIT.java new file mode 100644 index 0000000000..db61283ebf --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientIT.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import static io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfigurationTest.OPENAPI_TARGET_ROUTE_ID_NAME; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.ScriptProperty; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class OpenapiClientIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/openapi/streams/http") + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/streams/openapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(4096) + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/config") + .external("http0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("client.yaml") + @Specification({ + "${openapi}/create.pet/client", + "${http}/create.pet/server" + }) + @ScriptProperty("serverAddress \"zilla://streams/http0\"") + @Configure(name = OPENAPI_TARGET_ROUTE_ID_NAME, value = "4294967298") + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java new file mode 100644 index 0000000000..d50830204d --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; + +public class OpenapiServerIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/openapi/streams/http") + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/streams/openapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(4096) + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/config") + .external("openapi0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${http}/create.pet/client", + "${openapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("server-secure.yaml") + @Specification({ + "${http}/create.pet/client", + "${openapi}/create.pet/server" + }) + public void shouldCreateSecurePet() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("server.yaml") + @Specification({ + "${http}/reject.non.composite.origin/client" + }) + public void shouldRejectNonCompositeOrigin() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/pom.xml b/incubator/pom.xml index 846204631d..5a01348fa2 100644 --- a/incubator/pom.xml +++ b/incubator/pom.xml @@ -19,6 +19,7 @@ binding-amqp.spec binding-asyncapi.spec + binding-openapi.spec catalog-inline.spec catalog-schema-registry.spec exporter-otlp.spec @@ -29,6 +30,7 @@ binding-amqp binding-asyncapi + binding-openapi catalog-inline catalog-schema-registry @@ -58,6 +60,11 @@ binding-asyncapi ${project.version} + + ${project.groupId} + binding-openapi + ${project.version} + ${project.groupId} catalog-inline diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java index 7bb3baefd3..83b62ff766 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java @@ -17,6 +17,8 @@ import static java.util.function.Function.identity; +import java.util.function.Function; + public final class HttpAuthorizationConfig { public final String name; @@ -27,6 +29,12 @@ public static HttpAuthorizationConfigBuilder builder() return new HttpAuthorizationConfigBuilder<>(identity()); } + public static HttpAuthorizationConfigBuilder builder( + Function mapper) + { + return new HttpAuthorizationConfigBuilder(mapper); + } + HttpAuthorizationConfig( String name, HttpCredentialsConfig credentials) diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java index 5f16beb53b..6a7bcff7d3 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java @@ -26,19 +26,6 @@ public final class HttpAuthorizationConfigBuilder extends ConfigBuilder mapper) - { - this.mapper = mapper; - } - - @Override - @SuppressWarnings("unchecked") - protected Class> thisType() - { - return (Class>) getClass(); - } - public HttpAuthorizationConfigBuilder name( String name) { @@ -57,6 +44,20 @@ public T build() return mapper.apply(new HttpAuthorizationConfig(name, credentials)); } + + HttpAuthorizationConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + private HttpAuthorizationConfigBuilder credentials( HttpCredentialsConfig credentials) { diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java index 0271061d3c..bce304ea2f 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java @@ -101,7 +101,7 @@ public HttpRequestConfigBuilder> request() return new HttpRequestConfigBuilder<>(this::request); } - private HttpOptionsConfigBuilder authorization( + public HttpOptionsConfigBuilder authorization( HttpAuthorizationConfig authorization) { this.authorization = authorization; diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json index 2ae7449415..8c45102023 100644 --- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json @@ -147,88 +147,7 @@ } ] }, - "authorization": - { - "title": "Authorizations", - "type": "object", - "patternProperties": - { - "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": - { - "title": "Authorization", - "type": "object", - "properties": - { - "credentials": - { - "title": "Credentials", - "type": "object", - "properties": - { - "cookies": - { - "title": "Cookies", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - }, - "headers": - { - "title": "Headers", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - }, - "query": - { - "title": "Query Parameters", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - } - }, - "additionalProperties": false, - "anyOf": - [ - { - "required": - [ - "cookies" - ] - }, - { - "required": - [ - "headers" - ] - }, - { - "required": - [ - "query" - ] - } - ] - } - }, - "additionalProperties": false, - "required": - [ - "credentials" - ] - } - }, - "maxProperties": 1 - }, + "authorization": "$defs/options/binding/http/authorization", "overrides": { "title": "Overrides", @@ -469,5 +388,94 @@ ] } } + }, + { + "op": "add", + "path": "/$defs/options/binding/http", + "value": + { + "authorization": + { + "title": "Authorizations", + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "title": "Authorization", + "type": "object", + "properties": + { + "credentials": + { + "title": "Credentials", + "type": "object", + "properties": + { + "cookies": + { + "title": "Cookies", + "type": "object", + "additionalProperties": + { + "type": "string", + "pattern": ".*\\{credentials\\}.*" + } + }, + "headers": + { + "title": "Headers", + "type": "object", + "additionalProperties": + { + "type": "string", + "pattern": ".*\\{credentials\\}.*" + } + }, + "query": + { + "title": "Query Parameters", + "type": "object", + "additionalProperties": + { + "type": "string", + "pattern": ".*\\{credentials\\}.*" + } + } + }, + "additionalProperties": false, + "anyOf": + [ + { + "required": + [ + "cookies" + ] + }, + { + "required": + [ + "headers" + ] + }, + { + "required": + [ + "query" + ] + } + ] + } + }, + "additionalProperties": false, + "required": + [ + "credentials" + ] + } + }, + "maxProperties": 1 + } + } } ] diff --git a/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json b/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json index b1ef9202ab..dd2cc277ad 100644 --- a/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json +++ b/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json @@ -303,6 +303,12 @@ } ] }, + "options": + { + "binding": + { + } + }, "binding": { "title": "Binding",