Skip to content

Commit

Permalink
Define trait and extension for adding x- properties to openapi outp…
Browse files Browse the repository at this point in the history
…ut (#73)

* Define a trait and extension for adding `x-` properties to openapi output

Signed-off-by: Thomas Farr <tsfarr@amazon.com>

* Add small documentation example

Signed-off-by: Thomas Farr <tsfarr@amazon.com>

---------

Signed-off-by: Thomas Farr <tsfarr@amazon.com>
  • Loading branch information
Xtansia authored Apr 11, 2023
1 parent c7d6ea2 commit 7bcae6c
Show file tree
Hide file tree
Showing 15 changed files with 228 additions and 4 deletions.
22 changes: 22 additions & 0 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,28 @@ If you are retrospectively adding the API, Then you can refer to the following r
- [OpenSearch Documentation](https://opensearch.org/docs/latest)
- [Go through the serialisation logic in OpenSearch](https://github.com/opensearch-project/OpenSearch)

### OpenAPI Vendor Extensions

This repository includes a custom Smithy trait `@vendorExtensions` and accompanying build extension to enable adding custom OpenAPI specification extensions to operations in the converted OpenAPI output. It is used to add additional metadata about the operations to track the "namespaced" concept from the OpenSearch server and clients, and to account for when a single API operation being represented by multiple REST operations.

```smithy
use opensearch.openapi#vendorExtensions
@externalDocumentation(
"API Reference": "https://opensearch.org/docs/latest/api-reference/cat/cat-indices/"
)
@vendorExtensions(
"x-operation-group": "cat.indices",
"x-version-added": "1.0"
)
@http(method: "GET", uri: "/_cat/indices")
@documentation("Returns information about indices: number of primaries and replicas, document counts, disk size, ...")
operation CatIndices {
input: CatIndices_Input,
output: CatIndices_Output
}
```

## Adding a test-case for API definition

Once you've finished with the model API, follow the steps below to create a test-case.
Expand Down
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ buildscript {
classpath("software.amazon.smithy:smithy-openapi:$smithyVersion")
classpath("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
classpath("software.amazon.smithy:smithy-cli:$smithyVersion")
classpath("org.opensearch.smithy:openapi-traits") // Can't have a buildscript classpath dependency on a project, so need to use composite build & substitution
}
}

dependencies {
implementation("software.amazon.smithy:smithy-model:$smithyVersion")
implementation("software.amazon.smithy:smithy-linters:$smithyVersion")
implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
implementation("org.opensearch.smithy:openapi-traits")
}

spotless {
Expand Down
10 changes: 10 additions & 0 deletions model/cat/indices/cat_indicies/operations.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
$version: "2"
namespace OpenSearch

use opensearch.openapi#vendorExtensions

@externalDocumentation(
"OpenSearch Documentation": "https://opensearch.org/docs/latest/api-reference/cat/cat-indices/"
)
Expand All @@ -15,6 +17,10 @@ namespace OpenSearch
@http(method: "GET", uri: "/_cat/indices")
@suppress(["HttpUriConflict"])
@documentation("Returns information about indices: number of primaries and replicas, document counts, disk size, etc.")
@vendorExtensions(
"x-namespace": "cat"
"x-operation": "indices"
)
operation GetCatIndices {
input: GetCatIndicesInput,
output: GetCatIndicesOutput
Expand All @@ -25,6 +31,10 @@ operation GetCatIndices {
@http(method: "GET", uri: "/_cat/indices/{index}")
@suppress(["HttpUriConflict"])
@documentation("Returns information about indices: number of primaries and replicas, document counts, disk size, etc.")
@vendorExtensions(
"x-namespace": "cat"
"x-operation": "indices"
)
operation GetCatIndicesWithIndex {
input: GetCatIndicesWithIndexInput,
output: GetCatIndicesWithIndexOutput
Expand Down
25 changes: 25 additions & 0 deletions openapi-traits/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
java
`java-library`
id("com.diffplug.spotless").version("6.11.0")
}

group = "org.opensearch.smithy"

repositories {
mavenCentral()
}

dependencies {
implementation("software.amazon.smithy:smithy-openapi:1.26.0")
}

spotless {
kotlinGradle {
target("**/*.kts", "**/*.java", "**/*.smithy")

indentWithSpaces()
endWithNewline()
trimTrailingWhitespace()
}
}
1 change: 1 addition & 0 deletions openapi-traits/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = "openapi-traits"
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.opensearch.smithy.openapi.extensions;

import org.opensearch.smithy.openapi.extensions.mappers.VendorExtensionsJsonSchemaMapper;
import org.opensearch.smithy.openapi.extensions.mappers.VendorExtensionsOpenApiMapper;
import software.amazon.smithy.jsonschema.JsonSchemaMapper;
import software.amazon.smithy.openapi.fromsmithy.OpenApiMapper;
import software.amazon.smithy.openapi.fromsmithy.Smithy2OpenApiExtension;

import java.util.List;

public class VendorExtensionsExtension implements Smithy2OpenApiExtension {
@Override
public List<JsonSchemaMapper> getJsonSchemaMappers() {
return List.of(new VendorExtensionsJsonSchemaMapper());
}

@Override
public List<OpenApiMapper> getOpenApiMappers() {
return List.of(new VendorExtensionsOpenApiMapper());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.opensearch.smithy.openapi.extensions.mappers;

import org.opensearch.smithy.openapi.traits.VendorExtensionsTrait;
import software.amazon.smithy.jsonschema.JsonSchemaConfig;
import software.amazon.smithy.jsonschema.JsonSchemaMapper;
import software.amazon.smithy.jsonschema.Schema;
import software.amazon.smithy.model.shapes.Shape;

public class VendorExtensionsJsonSchemaMapper implements JsonSchemaMapper {
@Override
public Schema.Builder updateSchema(Shape shape, Schema.Builder schemaBuilder, JsonSchemaConfig config) {
shape.getTrait(VendorExtensionsTrait.class)
.ifPresent(trait -> trait.getNode()
.getMembers()
.forEach((k, v) -> schemaBuilder.putExtension(k.getValue(), v)));

return schemaBuilder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.opensearch.smithy.openapi.extensions.mappers;

import org.opensearch.smithy.openapi.traits.VendorExtensionsTrait;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.openapi.fromsmithy.Context;
import software.amazon.smithy.openapi.fromsmithy.OpenApiMapper;
import software.amazon.smithy.openapi.model.OperationObject;

public class VendorExtensionsOpenApiMapper implements OpenApiMapper {
@Override
public OperationObject updateOperation(Context<? extends Trait> context, OperationShape shape, OperationObject operation, String httpMethodName, String path) {
return shape.getTrait(VendorExtensionsTrait.class)
.map(trait -> {
OperationObject.Builder builder = operation.toBuilder();

trait.getNode()
.getMembers()
.forEach((k, v) -> builder.putExtension(k.getValue(), v));

return builder.build();
})
.orElse(operation);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.opensearch.smithy.openapi.traits;

import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.AbstractTrait;
import software.amazon.smithy.model.traits.AbstractTraitBuilder;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.utils.SmithyBuilder;
import software.amazon.smithy.utils.ToSmithyBuilder;

public final class VendorExtensionsTrait extends AbstractTrait implements ToSmithyBuilder<VendorExtensionsTrait> {
public static final ShapeId ID = ShapeId.from("opensearch.openapi#vendorExtensions");

private final ObjectNode node;

private VendorExtensionsTrait(Builder builder) {
super(ID, builder.getSourceLocation());
this.node = SmithyBuilder.requiredState("node", builder.node);
}

public static final class Provider extends AbstractTrait.Provider {
public Provider() {
super(ID);
}

@Override
public Trait createTrait(ShapeId target, Node value) {
ObjectNode node = value.expectObjectNode();
VendorExtensionsTrait trait = builder().sourceLocation(value).node(node).build();
trait.setNodeCache(value);
return trait;
}
}

public ObjectNode getNode() {
return this.node;
}

public static Builder builder() {
return new Builder();
}

@Override
protected Node createNode() {
return Node.objectNodeBuilder()
.sourceLocation(getSourceLocation())
.merge(node)
.build();
}

@Override
public Builder toBuilder() {
return builder()
.sourceLocation(getSourceLocation())
.node(node);
}

public static final class Builder extends AbstractTraitBuilder<VendorExtensionsTrait, Builder> {
private ObjectNode node;

private Builder() {
}

@Override
public VendorExtensionsTrait build() {
return new VendorExtensionsTrait(this);
}

public Builder node(ObjectNode node) {
this.node = node;
return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.opensearch.smithy.openapi.traits.VendorExtensionsTrait$Provider
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.opensearch.smithy.openapi.extensions.VendorExtensionsExtension
1 change: 1 addition & 0 deletions openapi-traits/src/main/resources/META-INF/smithy/manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
opensearch.openapi.smithy
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
$version: "2"

namespace opensearch.openapi

@trait(
selector: ":is(simpleType, list, map, structure, union, operation, member)"
)
map vendorExtensions {
@pattern("^x-.+$")
key: String
value: Document
}
3 changes: 3 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
rootProject.name = "opensearch-api-specification"

includeBuild("openapi-traits")
14 changes: 10 additions & 4 deletions smithy-build.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
{
"version": "1.0",
"plugins": {
"openapi": {
"service": "OpenSearch#OpenSearch",
"protocol": "aws.protocols#restJson1"
"projections": {
"full": {
"plugins": {
"openapi": {
"service": "OpenSearch#OpenSearch",
"protocol": "aws.protocols#restJson1",
"tags": true,
"useIntegerType": true
}
}
}
}
}

0 comments on commit 7bcae6c

Please sign in to comment.