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

Update externalDocumentation to a named map #363

Merged
merged 1 commit into from
Apr 8, 2020
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
22 changes: 22 additions & 0 deletions docs/source/guides/converting-to-openapi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,28 @@ openapi.use.xml (boolean)
Enables converting Smithy XML traits to OpenAPI XML properties. (this
feature is not yet implemented).

openapi.externalDocs ([string])
Limits the source of converted "externalDocs" fields to the specified
priority ordered list of names in an :ref:`externaldocumentation-trait`.
This list is case insensitive. By default, this is a list of the following
values: "Homepage", "API Reference", "User Guide", "Developer Guide",
"Reference", and "Guide".

.. code-block:: json

{
"version": "1.0",
"plugins": {
"openapi": {
"service": "smithy.example#Weather",
"openapi.externalDocs": [
"Homepage",
"Custom"
]
}
}
}

openapi.keepUnusedComponents (boolean)
Set to ``true`` to prevent unused components from being removed from the
created specification.
Expand Down
10 changes: 7 additions & 3 deletions docs/source/spec/core/documentation-traits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -245,17 +245,21 @@ These values use the same semantics and format as
-------------------------------

Summary
Provides a link to external documentation for a shape.
Provides named links to external documentation for a shape.
Trait selector
``*``
Value type
``string`` containing a valid URL.
``map`` of ``string`` containing a name to ``string`` containing a valid
URL.

.. tabs::

.. code-tab:: smithy

@externalDocumentation("https://www.example.com/")
@externalDocumentation(
"Homepage": "https://www.example.com/",
"API Reference": "https://www.example.com/api-ref",
)
service MyService {
version: "2006-03-01",
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"selector": "service"
},
"smithy.api#documentation": "Specifies the source of the caller identifier that will be used to throttle API methods that require a key.",
"smithy.api#externalDocumentation": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-api-key-source.html",
"smithy.api#externalDocumentation": {
"Developer Guide": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-api-key-source.html"
},
"smithy.api#tags": [
"internal"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use smithy.test#httpResponseTests
/// This examples adds headers to the input of a request and response by prefix.
@readonly
@http(uri: "/HttpPrefixHeaders", method: "GET")
@externalDocumentation("https://awslabs.github.io/smithy/spec/http.html#httpprefixheaders-trait")
@externalDocumentation("httpPrefixHeaders Trait": "https://awslabs.github.io/smithy/spec/http.html#httpprefixheaders-trait")
operation HttpPrefixHeaders {
input: HttpPrefixHeadersInputOutput,
output: HttpPrefixHeadersInputOutput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use smithy.test#httpResponseTests
/// This examples adds headers to the input of a request and response by prefix.
@readonly
@http(uri: "/HttpPrefixHeaders", method: "GET")
@externalDocumentation("https://awslabs.github.io/smithy/spec/http.html#httpprefixheaders-trait")
@externalDocumentation("httpPrefixHeaders Trait": "https://awslabs.github.io/smithy/spec/http.html#httpprefixheaders-trait")
operation HttpPrefixHeaders {
input: HttpPrefixHeadersInputOutput,
output: HttpPrefixHeadersInputOutput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
"smithy.api#trait": {
"selector": "resource"
},
"smithy.api#externalDocumentation": "https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html",
"smithy.api#externalDocumentation": {
"Reference": "https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html"
},
"smithy.api#documentation": "Specifies an ARN template for the resource."
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"traits": {
"smithy.api#required": true,
"smithy.api#documentation": "The signature version 4 service signing name to use in the credential scope when signing requests. This value SHOULD match the `arnNamespace` property of the `aws.api#service-trait`.",
"smithy.api#externalDocumentation": "https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html"
"smithy.api#externalDocumentation": {
"Reference": "https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html"
}
}
}
},
Expand All @@ -23,7 +25,9 @@
]
},
"smithy.api#documentation": "Signature Version 4 is the process to add authentication information to AWS requests sent by HTTP. For security, most requests to AWS must be signed with an access key, which consists of an access key ID and secret access key. These two keys are commonly referred to as your security credentials.",
"smithy.api#externalDocumentation": "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html"
"smithy.api#externalDocumentation": {
"Reference": "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html"
}
}
},
"aws.auth#unsignedPayload": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,112 @@

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import software.amazon.smithy.model.SourceException;
import software.amazon.smithy.model.SourceLocation;
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.utils.ToSmithyBuilder;

/**
* Provides a link to external documentation of a service or operation.
* Provides named links to external documentation.
*/
public final class ExternalDocumentationTrait extends StringTrait {
public final class ExternalDocumentationTrait extends AbstractTrait
implements ToSmithyBuilder<ExternalDocumentationTrait> {

public static final ShapeId ID = ShapeId.from("smithy.api#externalDocumentation");

public ExternalDocumentationTrait(String value, SourceLocation sourceLocation) {
super(ID, value, sourceLocation);
validateUrl(value, sourceLocation);
}
private final Map<String, String> urls;

public ExternalDocumentationTrait(String value) {
this(value, SourceLocation.NONE);
}
public ExternalDocumentationTrait(Builder builder) {
super(ID, builder.sourceLocation);
this.urls = Collections.unmodifiableMap(builder.urls);

public static final class Provider extends StringTrait.Provider<ExternalDocumentationTrait> {
public Provider() {
super(ID, ExternalDocumentationTrait::new);
}
urls.forEach((name, url) -> validateUrl(name, url, getSourceLocation()));
}

private static String validateUrl(String url, SourceLocation location) {
private static String validateUrl(String name, String url, SourceLocation location) {
try {
new URL(url);
return url;
} catch (MalformedURLException e) {
throw new SourceException("externalDocumentation must be a valid URL. Found " + url, location);
throw new SourceException(String.format("Each externalDocumentation value must be a valid URL. "
+ "Found \"%s\" for name \"%s\"", url, name), location);
}
}

/**
* Gets the external documentation names and links.
*
* @return returns the external documentation mapping.
*/
public Map<String, String> getUrls() {
return urls;
}

@Override
protected Node createNode() {
return ObjectNode.fromStringMap(urls);
}

@Override
public Builder toBuilder() {
Builder builder = builder().sourceLocation(getSourceLocation());
urls.forEach(builder::addUrl);
return builder;
}

/**
* @return Returns an external documentation trait builder.
*/
public static Builder builder() {
return new Builder();
}

/**
* Builder used to create the external documentation trait.
*/
public static final class Builder extends AbstractTraitBuilder<ExternalDocumentationTrait, Builder> {
private final Map<String, String> urls = new LinkedHashMap<>();

public Builder addUrl(String name, String url) {
urls.put(Objects.requireNonNull(name), Objects.requireNonNull(url));
return this;
}

public Builder removeUrl(String name) {
urls.remove(name);
return this;
}

public Builder clearUrls() {
urls.clear();
return this;
}

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

public static final class Provider implements TraitService {
@Override
public ShapeId getShapeId() {
return ID;
}

@Override
public ExternalDocumentationTrait createTrait(ShapeId target, Node value) {
Builder builder = builder().sourceLocation(value);
value.expectObjectNode().getMembers().forEach((k, v) -> {
builder.addUrl(k.expectStringNode().getValue(), v.expectStringNode().getValue());
});
return builder.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ string documentation

/// Provides a link to additional documentation.
@trait
string externalDocumentation
@length(min: 1)
map externalDocumentation {
kstich marked this conversation as resolved.
Show resolved Hide resolved
key: NonEmptyString,
value: NonEmptyString
}

/// Defines the list of authentication schemes supported by a service or operation.
@trait(selector: ":test(service, operation)")
Expand Down Expand Up @@ -108,21 +112,21 @@ structure authDefinition {
/// on a service or operation.
@trait(selector: "service")
@authDefinition
@externalDocumentation("https://tools.ietf.org/html/rfc2617.html")
@externalDocumentation("RFC 2617": "https://tools.ietf.org/html/rfc2617.html")
structure httpBasicAuth {}

/// Enables HTTP Digest Authentication as defined in RFC 2617
/// on a service or operation.
@trait(selector: "service")
@authDefinition
@externalDocumentation("https://tools.ietf.org/html/rfc2617.html")
@externalDocumentation("RFC 2617": "https://tools.ietf.org/html/rfc2617.html")
structure httpDigestAuth {}

/// Enables HTTP Bearer Authentication as defined in RFC 6750
/// on a service or operation.
@trait(selector: "service")
@authDefinition
@externalDocumentation("https://tools.ietf.org/html/rfc6750.html")
@externalDocumentation("RFC 6750": "https://tools.ietf.org/html/rfc6750.html")
structure httpBearerAuth {}

/// An HTTP-specific authentication scheme that sends an arbitrary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void getsMemberTraits() {
Shape target = StringShape.builder()
.id("foo.baz#Bar")
.addTrait(new DocumentationTrait("hi"))
.addTrait(new ExternalDocumentationTrait("http://example.com"))
.addTrait(ExternalDocumentationTrait.builder().addUrl("Ref", "http://example.com").build())
.build();
MemberShape member = MemberShape.builder()
.id("foo.baz#Bar$member")
Expand All @@ -101,7 +101,7 @@ public void getsMemberTraits() {
member.getMemberTrait(model, DocumentationTrait.class).get().getValue(),
equalTo("override"));
assertThat(
member.getMemberTrait(model, ExternalDocumentationTrait.class).get().getValue(),
member.getMemberTrait(model, ExternalDocumentationTrait.class).get().getUrls().get("Ref"),
equalTo("http://example.com"));
assertThat(
member.findMemberTrait(model, "documentation"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package software.amazon.smithy.model.traits;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasKey;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.model.SourceException;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.shapes.ShapeId;

public class ExternalDocumentationTraitTest {
@Test
public void loadsTrait() {
Node node = Node.parse("{\"API Reference\": \"https://foo.bar/api\","
+ "\"Usage Guide\": \"https://foo.bar/guide\"}");
ExternalDocumentationTrait trait = new ExternalDocumentationTrait.Provider()
.createTrait(ShapeId.from("ns.foo#baz"), node);

assertThat(trait.toNode(), equalTo(node));
assertThat(trait.toBuilder().build(), equalTo(trait));
assertThat(trait.getUrls(), hasKey("API Reference"));
assertThat(trait.getUrls().get("API Reference"), equalTo("https://foo.bar/api"));
assertThat(trait.getUrls(), hasKey("Usage Guide"));
assertThat(trait.getUrls().get("Usage Guide"), equalTo("https://foo.bar/guide"));
}

@Test
public void expectsValidUrls() {
Assertions.assertThrows(SourceException.class, () -> {
TraitFactory provider = TraitFactory.createServiceFactory();
provider.createTrait(ShapeId.from("smithy.api#externalDocumentation"),
ShapeId.from("ns.qux#foo"), Node.parse("{\"API Reference\": \"foobarapi\""));
});
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
[ERROR] ns.foo#Invalid: Error creating trait `externalDocumentation`: externalDocumentation must be a valid URL. Found invalid! | Model
[ERROR] ns.foo#Invalid: Error creating trait `externalDocumentation`: Each externalDocumentation value must be a valid URL. Found "invalid!" for name "Homepage" | Model
[ERROR] ns.foo#Invalid2: Error validating trait `externalDocumentation`: Value provided for `smithy.api#externalDocumentation` must have at least 1 entries, but the provided value only has 0 entries | TraitValue
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,25 @@
"type": "service",
"version": "2017-01-17",
"traits": {
"smithy.api#externalDocumentation": "https://www.example.com"
"smithy.api#externalDocumentation": {
"Homepage": "https://www.example.com"
}
}
},
"ns.foo#Invalid": {
"type": "service",
"version": "2017-01-17",
"traits": {
"smithy.api#externalDocumentation": "invalid!"
"smithy.api#externalDocumentation": {
"Homepage": "invalid!"
}
}
},
"ns.foo#Invalid2": {
"type": "service",
"version": "2017-01-17",
"traits": {
"smithy.api#externalDocumentation": {}
}
}
}
Expand Down
Loading