Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow adding member docs to $ref #2402

Merged
merged 2 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,58 @@ disableIntEnums (``boolean``)
}


.. _generate-openapi-setting-addReferenceDescriptions:

addReferenceDescriptions (``boolean``)
Set to ``true`` to add the ``description`` property to ``$ref`` members
with the value of the member's :ref:`documentation-trait` trait.

.. important::

This property is only supported when :ref:`version <generate-openapi-setting-version>`
is set to ``3.1.0``.

By default, ``$ref`` members will have no ``description``:

.. code-block:: smithy
:caption: example.smithy

structure Foo {
/// Member docs
bar: Bar
}

.. code-block:: json
:caption: Example.openapi.json

{
"Foo": {
"type": "object",
"properties": {
"bar": {
"$ref": "#/definitions/Bar"
}
}
}
}

With this enabled, member docs will be added:

.. code-block:: json
:caption: Example.openapi.json

{
"Foo": {
"type": "object",
"properties": {
"bar": {
"$ref": "#/definitions/Bar",
"description": "Member docs"
}
}
}
}

----------------
Security schemes
----------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public String toString() {
private boolean useIntegerType;
private boolean disableDefaultValues = false;
private boolean disableIntEnums = false;
private boolean addReferenceDescriptions = false;

public JsonSchemaConfig() {
nodeMapper.setWhenMissingSetter(NodeMapper.WhenMissing.IGNORE);
Expand Down Expand Up @@ -456,4 +457,26 @@ public JsonSchemaVersion getJsonSchemaVersion() {
public void setJsonSchemaVersion(JsonSchemaVersion schemaVersion) {
this.jsonSchemaVersion = Objects.requireNonNull(schemaVersion);
}

/**
* Whether to add the {@code description} property to Schema References
* when converting Smithy member shapes into JSON Schema with the value
* of the member's documentation.
*
* <p>Defaults to {@code false}.</p>
*
* @return Whether to add descriptions to Schema References.
*/
public boolean getAddReferenceDescriptions() {
return addReferenceDescriptions;
}

/**
* Sets whether the {@code description} property should be added to Schema References.
*
* @param addReferenceDescriptions Whether to add descriptions to Schema References
*/
public void setAddReferenceDescriptions(boolean addReferenceDescriptions) {
this.addReferenceDescriptions = addReferenceDescriptions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ private Schema createRef(MemberShape member) {
if (member.hasTrait(DeprecatedTrait.class) && getJsonSchemaVersion() != JsonSchemaVersion.DRAFT07) {
refBuilder.deprecated(true);
}

if (converter.getConfig().getAddReferenceDescriptions()) {
descriptionMessage(member).ifPresent(refBuilder::description);
}

// Wrap the ref and default in an allOf if disableDefaultValues has been not been disabled on config.
if (member.hasTrait(DefaultTrait.class) && !converter.getConfig().getDisableDefaultValues()) {
Schema def = Schema.builder().defaultValue(member.expectTrait(DefaultTrait.class).toNode()).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -887,4 +887,24 @@ public void dontAddDeprecatedTraitOnAMemberWhenOldVersion() {
Schema memberSchema = document.getRootSchema().getProperties().get("member");
assertThat(memberSchema.isDeprecated(), equalTo(false));
}

@Test
public void canAddMemberDocumentation() {
Model model = Model.assembler()
.addImport(getClass().getResource("member-documentation.smithy"))
.assemble()
.unwrap();

JsonSchemaConfig config = new JsonSchemaConfig();
config.setAddReferenceDescriptions(true);
SchemaDocument document = JsonSchemaConverter.builder()
.config(config)
.model(model)
.build()
.convert();

Node expected = Node.parse(
IoUtils.toUtf8String(getClass().getResourceAsStream("member-documentation.jsonschema.json")));
Node.assertEquals(document.toNode(), expected);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"definitions": {
"Foo": {
"type": "object",
"properties": {
"foo": {
"type": "string",
"description": "simple docs"
},
"bar": {
"$ref": "#/definitions/Bar",
"description": "structure docs"
}
}
},
"Bar": {
"type": "object"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
$version: "2.0"

namespace smithy.example

structure Foo {
/// simple docs
foo: String

/// structure docs
bar: Bar
}

structure Bar {}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import software.amazon.smithy.model.validation.ValidationUtils;
import software.amazon.smithy.openapi.OpenApiConfig;
import software.amazon.smithy.openapi.OpenApiException;
import software.amazon.smithy.openapi.OpenApiVersion;
import software.amazon.smithy.openapi.model.ComponentsObject;
import software.amazon.smithy.openapi.model.InfoObject;
import software.amazon.smithy.openapi.model.OpenApi;
Expand Down Expand Up @@ -174,6 +175,12 @@ private ConversionEnvironment<? extends Trait> createConversionEnvironment(Model
throw new OpenApiException("openapi is missing required property, `service`");
}

if (config.getAddReferenceDescriptions() && config.getVersion() == OpenApiVersion.VERSION_3_0_2) {
throw new OpenApiException(
"openapi property `addReferenceDescriptions` requires openapi version 3.1.0 or later.\n"
+ "Suggestion: Add `\"version\"`: \"3.1.0\" to your openapi config.");
}

// Find the service shape.
ServiceShape service = model.getShape(serviceShapeId)
.orElseThrow(() -> new IllegalArgumentException(String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -639,4 +640,37 @@ public void removesMixins() {

Node.assertEquals(result, expectedNode);
}

@Test
public void convertsMemberDocumentation() {
Model model = Model.assembler()
.addImport(getClass().getResource("documentation-test-members.smithy"))
.discoverModels()
.assemble()
.unwrap();
OpenApiConfig config = new OpenApiConfig();
config.setService(ShapeId.from("smithy.example#MyDocs"));
config.setVersion(OpenApiVersion.VERSION_3_1_0);
config.setAddReferenceDescriptions(true);
Node result = OpenApiConverter.create().config(config).convertToNode(model);
Node expectedNode = Node.parse(IoUtils.toUtf8String(
getClass().getResourceAsStream("documentation-test-members.openapi.json")));

Node.assertEquals(result, expectedNode);
}

@Test
public void convertingMemberDocsRequired3_1() {
Model model = Model.assembler()
.addImport(getClass().getResource("documentation-test-members.smithy"))
.discoverModels()
.assemble()
.unwrap();
OpenApiConfig config = new OpenApiConfig();
config.setService(ShapeId.from("smithy.example#MyDocs"));
config.setAddReferenceDescriptions(true);
OpenApiConverter converter = OpenApiConverter.create().config(config);

assertThrows(OpenApiException.class, () -> converter.convertToNode(model));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"openapi": "3.1.0",
"info": {
"title": "MyDocs",
"version": "2018-01-01",
"description": "Service"
},
"paths": {
"/": {
"get": {
"description": "Operation",
"operationId": "MyDocsOperation",
"responses": {
"200": {
"description": "MyDocsOperation 200 response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MyDocsOperationResponseContent"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"MyDocsOperationResponseContent": {
"type": "object",
"description": "Output",
"properties": {
"foo": {
"type": "string",
"description": "foo member."
},
"nested": {
"$ref": "#/components/schemas/Nested",
"description": "nested member."
}
}
},
"Nested": {
"type": "object",
"description": "Nested",
"properties": {
"baz": {
"type": "string"
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
$version: "2.0"

namespace smithy.example

/// Service
@aws.protocols#restJson1
service MyDocs {
version: "2018-01-01",
operations: [MyDocsOperation]
}

/// Operation
@http(method: "GET", uri: "/")
@readonly
operation MyDocsOperation {
output: Output
}

/// Output
structure Output {
/// foo member.
foo: String,

/// nested member.
nested: Nested,
}

/// Nested
structure Nested {
baz: String,
}
Loading