From e771467282f39d2137b9029dc386348e424964ae Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Tue, 16 Nov 2021 11:55:57 -0800 Subject: [PATCH] Add Unit type and @input and @output traits This commit introduces the `smithy.api#Unit` type that that is used for operations with no meaningful input or output, and for tagged union members with no meaningful value. Operations will now, by default, target `smithy.api#Union` if they do not define input or output. This commit also introduces the `@input` and `@output` traits that are used to specialize structures as for use only as the input or output of a single operation. The `@input` trait provides structures with more relaxed backward compatibility constraints so that, for example, when things like the `@default` value trait are added, it is considered a backward compatible change to remove the `@required` trait from the member of a structure marked with the `@input` trait. New methods were added to smithy-model to account for the fact that operations always now have input and output shapes. The existing methods that return Optional shape IDs and structures remain but are marked as deprecated. Any usage of these deprecated methods were updated throughout the monorepo to use the newly introduced methods (this accounts for a lot of the churn). If an operation defines no input or output, a WARNING is emitted. If an operation defines input or output other than `smithy.api#Unit` and the targeted shapes aren't marked with `@input` or `@output` a validation event is emitted. This also accounts for a lot of churn in test cases (either to add suppressions or to add input and output). Due to the suppressed events, Smithy's errorfiles test runner was updated to no longer require that SUPPRESSED validation events are explicitly listed in errorfiles (they can be but don't have to be). Along with updating every operation to define input and output shapes, this change also removes many JSON AST examples. These examples were unnecessary and a liability for the team to maintain. The appropriate place to document how the IDL and JSON AST interact is in the specification language around the IDL and JSON AST, not spread across the entire specification. Alternatively, I could have edited each impacted AST example, but I worried that I would add mistakes and they are not validated against the IDL examples. --- .../operation-input-output-and-unit-types.md | 378 ++++++++++ docs/source/1.0/guides/evolving-models.rst | 13 +- docs/source/1.0/guides/model-linters.rst | 24 - .../1.0/spec/aws/aws-cloudformation.rst | 703 +++++++++--------- docs/source/1.0/spec/aws/aws-core.rst | 228 ++---- .../1.0/spec/aws/aws-ec2-query-protocol.rst | 2 + .../1.0/spec/aws/aws-query-protocol.rst | 3 + .../aws/customizations/s3-customizations.rst | 85 +-- docs/source/1.0/spec/core/behavior-traits.rst | 291 ++------ .../1.0/spec/core/documentation-traits.rst | 17 +- docs/source/1.0/spec/core/endpoint-traits.rst | 129 +--- docs/source/1.0/spec/core/http-traits.rst | 360 ++++----- docs/source/1.0/spec/core/model.rst | 459 ++++++------ docs/source/1.0/spec/core/prelude-model.rst | 5 + docs/source/1.0/spec/core/resource-traits.rst | 49 +- docs/source/1.0/spec/core/selectors.rst | 1 + docs/source/1.0/spec/core/stream-traits.rst | 421 +++-------- .../1.0/spec/core/type-refinement-traits.rst | 59 ++ .../spec/http-protocol-compliance-tests.rst | 250 ++----- docs/source/1.0/spec/mqtt.rst | 291 ++------ docs/source/1.0/spec/waiters.rst | 7 +- docs/source/quickstart.rst | 675 +++++------------ .../cognito-user-pools-security.openapi.json | 2 +- .../cors-explicit-options.openapi.json | 4 +- .../openapi/cors-model.openapi.json | 8 +- .../cors-with-additional-headers.openapi.json | 8 +- ...stom-gateway-response-headers.openapi.json | 4 +- .../greedy-labels-for-rest.openapi.json | 2 +- .../http-api-cors-wildcards.openapi.json | 4 +- .../openapi/http-api-cors.openapi.json | 4 +- .../openapi/non-numeric-floats.openapi.json | 2 +- ...eration-http-api-key-security.openapi.json | 2 +- .../openapi/substitution-not-performed.json | 2 +- .../openapi/substitution-performed.json | 2 +- .../traits/errorfiles/valid-integration.json | 18 + .../traits/CfnResourceIndex.java | 10 +- .../additionalschemas-conflict.smithy | 5 + .../errorfiles/deconflict-by-excluding.smithy | 5 + .../errorfiles/lifecycle-conflict.smithy | 7 + .../awsJson1_0/empty-input-output.smithy | 9 +- .../model/awsJson1_0/endpoint-paths.smithy | 2 +- .../model/awsJson1_0/endpoints.smithy | 7 +- .../model/awsJson1_0/errors.smithy | 7 + .../model/awsJson1_0/json-structs.smithy | 13 +- .../model/awsJson1_0/unions.smithy | 13 +- .../awsJson1_1/optional-input-output.smithy | 14 +- .../services/machinelearning.smithy | 2 + .../model/awsQuery/empty-input-output.smithy | 7 +- .../model/restJson1/empty-input-output.smithy | 54 +- .../restJson1/http-prefix-headers.smithy | 29 +- .../model/restJson1/main.smithy | 4 +- .../malformedRequests/malformed-accept.smithy | 4 +- .../model/restJson1/unions.smithy | 70 ++ .../model/shared-types.smithy | 51 +- .../traits/HttpChecksumTraitValidator.java | 13 +- .../CleanClientDiscoveryTraitTransformer.java | 3 +- .../ClientEndpointDiscoveryIndex.java | 8 +- .../ClientEndpointDiscoveryValidator.java | 17 +- .../META-INF/smithy/aws.protocols.json | 2 +- .../ClientEndpointDiscoveryIdTraitTest.java | 2 +- .../clientendpointdiscovery/test-model.json | 24 + .../endpoint-error.smithy | 19 +- .../endpoint-shape-not-structure.json | 12 + .../endpoints-not-list.json | 12 + .../invalid-endpoint-structure.json | 12 + .../invalid-input-shape.errors | 2 +- .../invalid-input-shape.json | 32 +- .../no-endpoints-member.errors | 2 +- .../no-endpoints-member.json | 12 + .../clientendpointdiscovery/no-input.errors | 1 + .../clientendpointdiscovery/no-input.json | 27 +- .../clientendpointdiscovery/no-output.errors | 3 +- .../clientendpointdiscovery/no-output.json | 9 + .../http-checksum-header-conflicts.smithy | 6 + .../http-checksum-member-enums.smithy | 8 + .../errorfiles/http-checksum-trait.errors | 3 - .../errorfiles/http-checksum-trait.smithy | 14 +- ...o-not-support-list-set-map-payloads.errors | 4 +- ...o-not-support-list-set-map-payloads.smithy | 10 +- ...-protocols-do-not-support-documents.smithy | 5 + .../codegen/core/TopologicalIndexTest.java | 8 +- .../evaluators/AddedOperationInputOutput.java | 69 +- .../evaluators/ChangedOperationInput.java | 8 +- .../evaluators/ChangedOperationOutput.java | 8 +- .../evaluators/RemovedOperationInput.java | 16 +- .../evaluators/RemovedOperationOutput.java | 16 +- .../software.amazon.smithy.diff.DiffEvaluator | 3 - .../AddedOperationInputOutputTest.java | 97 --- .../evaluators/RemovedOperationInputTest.java | 45 -- .../RemovedOperationOutputTest.java | 45 -- .../InputOutputStructureReuseValidator.java | 26 +- .../MissingPaginatedTraitValidator.java | 32 +- .../contains-reserved-words-validator.json | 63 +- .../input-output-structure-reuse.json | 6 + ...missing-documentation-selector-test.errors | 5 - .../missing-documentation-selector-test.json | 88 --- .../missing-paginated-trait-validator.json | 61 +- ...tandard-operation-verb-validator-test.json | 4 + .../model/knowledge/EventStreamIndex.java | 8 +- .../model/knowledge/HttpBindingIndex.java | 8 +- .../knowledge/IdentifierBindingIndex.java | 4 +- .../model/knowledge/OperationIndex.java | 118 ++- .../model/knowledge/PaginatedIndex.java | 8 +- .../smithy/model/loader/AstModelLoader.java | 8 + .../smithy/model/loader/IdlModelParser.java | 9 + .../smithy/model/loader/LoaderUtils.java | 38 + .../smithy/model/loader/ModelAssembler.java | 9 +- .../smithy/model/loader/ModelValidator.java | 24 +- .../model/neighbor/NeighborVisitor.java | 4 +- .../smithy/model/selector/PathFinder.java | 7 +- .../smithy/model/shapes/ModelSerializer.java | 4 +- .../smithy/model/shapes/OperationShape.java | 57 +- .../shapes/SmithyIdlModelSerializer.java | 13 +- .../smithy/model/traits/InputTrait.java | 41 + .../smithy/model/traits/OutputTrait.java | 41 + .../smithy/model/traits/UnitTypeTrait.java | 43 ++ .../plugins/CleanOperationStructures.java | 20 +- .../validation/testrunner/SmithyTestCase.java | 3 + .../validators/ExamplesTraitValidator.java | 32 +- .../validators/HostLabelTraitValidator.java | 27 +- .../validators/HttpLabelTraitValidator.java | 19 +- .../validators/HttpPayloadValidator.java | 6 +- .../validators/HttpUriConflictValidator.java | 18 +- .../validators/OperationValidator.java | 234 ++++++ .../validators/PaginatedTraitValidator.java | 46 +- .../validators/UnitTypeValidator.java | 72 ++ ...re.amazon.smithy.model.traits.TraitService | 3 + ...e.amazon.smithy.model.validation.Validator | 2 + .../amazon/smithy/model/loader/prelude.smithy | 22 + .../model/knowledge/OperationIndexTest.java | 9 +- .../smithy/model/traits/InputTraitTest.java | 25 + .../smithy/model/traits/OutputTraitTest.java | 25 + .../model/transform/ModelTransformerTest.java | 9 +- .../detects-bad-syntactic-shape-ids.smithy | 11 +- .../loader/dupe-operation-binding.smithy | 1 + .../errorfiles/loader/suppression-test.smithy | 5 + .../auth/auth-trait-invalid-shape-id.json | 6 + ...th-trait-must-target-auth-definitions.json | 6 + ...uth-trait-must-target-service-schemes.json | 6 + .../event-stream-with-errors.smithy | 5 + .../examples-trait-validator.errors | 4 +- .../validators/examples-trait-validator.json | 12 + .../validators/host-request-validator.errors | 6 +- .../validators/host-request-validator.json | 8 + ...p-api-key-scheme-trait-validator-test.json | 10 +- .../http-bindings-missing-validator.json | 42 ++ .../http-checksum-required-trait.json | 8 + .../http-header-tchar-validator.json | 12 + .../http-method-semantics-validator.json | 20 +- .../http-request-response-validator.errors | 4 +- .../http-request-response-validator.json | 8 + ...ttp-response-code-semantics-validator.json | 6 + .../validators/http-trait-conflicts.json | 3 + .../http-uri-conflict-validator.json | 8 + .../lifecycle/valid-implicit-lifecycles.json | 8 + .../validates-idempotent-lifecycles.json | 8 + ...es-instance-and-collection-operations.json | 8 + .../validates-readonly-lifecycles.json | 8 + .../linters/emit-each-selector-validator.json | 12 + ...ion-stops-validating-on-core-errors.smithy | 6 +- .../no-inline-document-support.smithy | 7 +- .../validators/no-replace-trait.json | 14 + .../operation/ambiguous-name.errors | 4 + .../operation/ambiguous-name.smithy | 24 + ...lid-input-output-operation-bindings.errors | 4 + ...lid-input-output-operation-bindings.smithy | 15 + ...valid-member-target-to-input-output.errors | 2 + ...valid-member-target-to-input-output.smithy | 13 + ...alid-multiple-operations-same-shape.errors | 4 + ...alid-multiple-operations-same-shape.smithy | 18 + .../valid-input-output-traits.errors | 0 .../valid-input-output-traits.smithy | 13 + .../paginated/paginated-deeply-nested.errors | 4 +- .../paginated/paginated-deeply-nested.json | 29 +- .../paginated/paginated-invalid.errors | 8 +- .../paginated/paginated-invalid.json | 12 +- .../paginated/paginated-map-tokens.errors | 4 +- .../paginated/paginated-map-tokens.json | 20 +- .../paginated/paginated-targets.json | 6 + .../paginated/paginated-valid-merge.json | 6 + .../validators/paginated/paginated-valid.json | 8 + .../validators/private-access.errors | 4 +- .../errorfiles/validators/private-access.json | 76 +- .../validators/request-token.errors | 3 +- .../errorfiles/validators/request-token.json | 28 +- .../required-trait-sanity-check.errors | 4 +- .../required-trait-sanity-check.json | 15 +- ...resource-must-bind-parent-identifiers.json | 14 +- .../resource-operation-binding-validator.json | 51 +- .../validators/sensitive-trait.json | 22 +- .../service-conflict-validator.json | 9 + .../validators/service-rename/conflict.smithy | 5 + .../error-rename-invalid.smithy | 8 + .../service-rename/invalid-identifier.smithy | 5 + .../service-rename/member-invalid.smithy | 5 + .../service-rename/operation-invalid.smithy | 11 +- .../redundant-rename-invalid.smithy | 5 + .../service-rename/shape-same-name.smithy | 7 +- .../validators/single-operation-binding.json | 6 + .../validators/streaming-trait-event.json | 8 + .../validators/streaming-trait.errors | 4 +- .../validators/streaming-trait.json | 40 +- .../validators/target-validator.json | 6 + .../errorfiles/validators/trait-target.json | 12 + .../validators/unit/invalid-members.errors | 4 + .../validators/unit/invalid-members.smithy | 20 + .../validators/unit/valid-operation.errors | 0 .../validators/unit/valid-operation.smithy | 8 + .../validators/unit/valid-union.errors | 0 .../validators/unit/valid-union.smithy | 9 + .../unreferenced-shape-validator.json | 6 + .../cases/service-shapes.smithy | 7 +- .../mqtt/traits/ResolvedTopicIndex.java | 10 +- .../smithy/mqtt/traits/TopicBinding.java | 11 + .../validators/MqttPublishInputValidator.java | 40 +- .../MqttSubscribeInputValidator.java | 35 +- .../validators/MqttTopicLabelValidator.java | 45 +- .../META-INF/smithy/smithy.api.mqtt.smithy | 2 +- .../errorfiles/dropped-mqtt-headers.smithy | 5 +- .../mqtt/traits/errorfiles/job-service.smithy | 9 + .../mqtt-operations-with-errors.smithy | 6 +- .../errorfiles/mqtt-topic-labels.errors | 2 +- .../errorfiles/mqtt-topic-labels.smithy | 42 +- .../publish-with-eventstream.smithy | 6 +- .../subscribe-input-missing-label.smithy | 12 +- .../subscribe-missing-output.smithy | 3 +- .../subscribe-operation-initial-event.smithy | 6 +- ...ribe-operation-missing-event-stream.smithy | 2 + .../traits/errorfiles/topic-conflicts.smithy | 28 +- .../openapi/fromsmithy/OpenApiConverter.java | 14 +- .../protocols/AbstractRestProtocol.java | 13 +- .../protocols/AwsRestJson1ProtocolTest.java | 8 +- .../mixed-security-service.openapi.json | 8 +- .../adds-header-mediatype-format.openapi.json | 2 +- .../adds-header-timestamp-format.openapi.json | 2 +- .../adds-json-document-bodies.openapi.json | 2 +- .../adds-path-timestamp-format.openapi.json | 2 +- .../adds-query-timestamp-format.openapi.json | 2 +- .../aws-rest-json-uses-jsonname.openapi.json | 2 +- ...name-parameter-without-suffix.openapi.json | 2 +- .../protocols/greedy-labels.openapi.json | 2 +- .../security/awsv4-security.openapi.json | 2 +- .../http-api-key-bearer-security.openapi.json | 2 +- .../http-api-key-security.openapi.json | 2 +- .../security/http-basic-security.openapi.json | 2 +- .../http-digest-security.openapi.json | 2 +- .../tagged-service-all-tags.openapi.json | 6 +- ...-service-empty-supported-tags.openapi.json | 6 +- ...tagged-service-supported-tags.openapi.json | 6 +- .../unsupported-http-method.openapi.json | 16 +- .../HttpRequestTestsInputValidator.java | 2 +- .../HttpResponseTestsOutputValidator.java | 2 +- .../all-malformed-request-features.smithy | 7 +- .../errorfiles/all-request-features.smithy | 7 +- .../errorfiles/all-response-features.smithy | 5 + .../errorfiles/detects-duplicate-ids.smithy | 23 +- ...nvalid-malformed-request-parameters.smithy | 7 +- ...-invalid-malformed-request-response.smithy | 7 +- .../errorfiles/detects-invalid-params.smithy | 12 +- .../traits/errorfiles/invalid-json.smithy | 7 +- .../errorfiles/invalid-xml-with-dtd.smithy | 7 +- .../traits/errorfiles/invalid-xml.smithy | 7 +- .../missing-vendor-params-shape.smithy | 7 +- .../errorfiles/timestamp-validation.smithy | 7 +- .../traits/errorfiles/valid-json.smithy | 7 +- .../traits/errorfiles/valid-xml.smithy | 7 +- .../vendor-params-validation.smithy | 12 +- .../waiters/WaiterMatcherValidator.java | 34 +- ...cannot-wait-on-streaming-operations.smithy | 14 +- ...emits-danger-and-warning-typechecks.smithy | 5 + ...inputOutput-operation-with-no-input.errors | 0 ...nputOutput-operation-with-no-input.smithy} | 2 + ...nputOutput-operation-with-no-output.errors | 1 + ...putOutput-operation-with-no-output.smithy} | 2 + .../invalid-boolean-expected-value.smithy | 5 + .../errorfiles/invalid-errorType.smithy | 5 + ...inputOutput-operation-with-no-input.errors | 1 - ...nputOutput-operation-with-no-output.errors | 1 - .../invalid-inputoutput-path.smithy | 2 + .../errorfiles/invalid-jmespath-syntax.smithy | 5 + ...alid-output-structure-member-access.smithy | 2 + .../errorfiles/invalid-return-types.smithy | 5 + .../minDelay-greater-than-maxDelay.smithy | 5 + .../errorfiles/not-uppercamelcase.smithy | 5 + .../errorfiles/output-on-bad-shapes.errors | 2 +- .../errorfiles/output-on-bad-shapes.smithy | 1 + .../errorfiles/valid-inputoutput.smithy | 2 + .../waiters/errorfiles/valid-waiters.smithy | 5 + .../waiter-missing-success-state.smithy | 10 +- .../errorfiles/waiter-name-conflicts.smithy | 16 +- 290 files changed, 4554 insertions(+), 3637 deletions(-) create mode 100644 designs/operation-input-output-and-unit-types.md delete mode 100644 smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedOperationInputOutputTest.java delete mode 100644 smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationInputTest.java delete mode 100644 smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationOutputTest.java delete mode 100644 smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-documentation-selector-test.errors delete mode 100644 smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-documentation-selector-test.json create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/traits/InputTrait.java create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/traits/OutputTrait.java create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/traits/UnitTypeTrait.java create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/OperationValidator.java create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/UnitTypeValidator.java create mode 100644 smithy-model/src/test/java/software/amazon/smithy/model/traits/InputTraitTest.java create mode 100644 smithy-model/src/test/java/software/amazon/smithy/model/traits/OutputTraitTest.java create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/ambiguous-name.errors create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/ambiguous-name.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-input-output-operation-bindings.errors create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-input-output-operation-bindings.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-member-target-to-input-output.errors create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-member-target-to-input-output.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-multiple-operations-same-shape.errors create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-multiple-operations-same-shape.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/valid-input-output-traits.errors create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/valid-input-output-traits.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/invalid-members.errors create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/invalid-members.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-operation.errors create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-operation.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-union.errors create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-union.smithy create mode 100644 smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-input.errors rename smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/{invalid-inputOutput-operation-with-no-input.smithy => inputOutput-operation-with-no-input.smithy} (92%) create mode 100644 smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-output.errors rename smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/{invalid-inputOutput-operation-with-no-output.smithy => inputOutput-operation-with-no-output.smithy} (92%) delete mode 100644 smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-input.errors delete mode 100644 smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-output.errors diff --git a/designs/operation-input-output-and-unit-types.md b/designs/operation-input-output-and-unit-types.md new file mode 100644 index 00000000000..10ad509605e --- /dev/null +++ b/designs/operation-input-output-and-unit-types.md @@ -0,0 +1,378 @@ +# Operation input, output, and unit types + +* **Author**: Michael Dowling +* **Created**: 2021-11-11 + +## Abstract + +This proposal introduces new traits and shapes that makes operation inputs +and outputs more explicit resulting in a simplified semantic model, simplified +code generation, the ability for operations to opt-in to more flexible +backward compatibility semantics, and more expressiveness for tagged unions. +This is achieved through introducing the `@input` and `@output` traits to +specialize a structure as the input or output of a single operation, and +introducing a built-in [unit](https://en.wikipedia.org/wiki/Unit_type) shape +to explicitly indicate that an operation has no meaningful input or output and +that a tagged union member has no meaningful value. + +## Motivation + +### Best practices for defining operations are too easy to miss + +Operations in Smithy prior to this proposal can have no input, no output, +or target any structure. A longstanding best-practice for Smithy models is +to always define a dedicated input and output structure for every operation, +even if an operation at its inception has no meaningful input or output. +This allows input members and output members to be added over time if ever +needed, which is important for services that plan to stay in production for +decades. + +Reusing input and output shapes for multiple operations can hinder how a +service can evolve over time. For example, if operations diverge over time +but share the same input structure, the operations might have input members +that have to resort to documentation to caution users that a member is only +used when calling certain operations but not others. + +### Operation backward compatibility semantics push too much complexity onto code generators + +It is now considered backward compatible for an operation to change from no +input or output to defining input and output, and this has resulted in +pushing complexity onto Smithy code generators. Smithy code generators today +often generate dedicated "synthetic" input and output shapes for each +operation to account for this kind of model evolution. Generators +essentially make a copy of shapes used as input and output, and in the case +that a shape isn't defined for an operation, they generate one. This allows +the generated code to add context-specific functionality to shapes used as +operation input or output, like methods for customizing middleware, adding +HTTP headers, etc. + +The reason for this backward compatibility affordance is largely because +service teams writing models are unaware that defining input and output shapes +for all operations is a best-practice. Its consequence is that each Smithy code +generation project needs to internalize all the nuance of synthetic input and +output shapes independently and figure out how to account for it in their +generators. + +## Proposal + +This proposal makes the following changes that impact both Smithy 1.0 and +Smithy 2.0: + +1. The input and output of operations defaults to `smithy.api#Unit`, a shape + in the Smithy prelude that represents the lack of a meaningful value. +2. A `Unit` shape will be added to the Smithy prelude. This shape is used when + an operation truly has no input or output, and for tagged union members with + no meaningful value. +3. `@input` and `@output` traits will be added that specialize a structure as + the input or output of a single operation. Shapes marked with the `@input` + and `@output` traits can only be referenced by a single operation, and + cannot act as both input and output. +4. A WARNING will be emitted for operations that target shapes that are not + marked with the `@input` and `@ouput` traits. +5. The `@input` and `@output` traits will automatically be applied to input and + output shapes that are defined with the inline operation input and output + syntax being introduced in Smithy IDL 2.0. + +### `@input` and `@output` traits + +`@input` and `@output` traits specialize a structure as allowed for use only +as top-level operation input and output. Shapes marked with these traits +cannot be targeted by members or used in any other way than to serve as the +input or output of a single operation. + +The following example is a valid use of `@input` and `@output` traits: + +``` +operation GetFoo { + input: GetFooInput, + output: GetFooOutput +} + +@input +structure GetFooInput {} + +@output +structure GetFooOutput {} +``` + +The following structure is invalid because a member targets a shape marked +with the `@input` trait: + +``` +structure Hello { + hi: GetFooInput // <- ERROR +} +``` + +The `@input` and `@output` traits make code generation easier because +generators can use the defined shape as-is without needing to generate a +synthetic shape. These traits also encourage modelers to define operations +that utilize known best practices, ensuring operations can easily evolve over +time. + +#### @input and @output trait definitions + +The `@input` and `@output` traits are defined in the prelude as: + +``` +/// Specializes a structure for use only as the input of a single operation. +@trait(selector: "structure", conflicts: [output, error]) +@tags(["diff.error.const"]) +structure input {} + +/// Specializes a structure for use only as the output of a single operation. +@trait(selector: "structure", conflicts: [input, error]) +@tags(["diff.error.const"]) +structure output {} +``` + +#### Relaxed backward compatibility semantics + +Structures marked with the `@input` trait will have more relaxed backward +compatibility semantics in that the `@required` trait can be freely removed +from their members. Other proposals are being explored in Smithy that allow +code generators to use traits like `@required` and `@default` to generate +more idiomatic code without sacrificing too much of a service's ability to +evolve their models over time. Code generators SHOULD take these relaxed +backward compatibility semantics into account when generating code for +structures marked with the `@input` trait to avoid breaking previously +generated clients as models evolve. + +#### Automatic application using inline syntax + +To encourage their use and remove the burden of needing to apply the `@input` +and `@output` traits to structures, the `@input` and `@output` traits will be +added automatically when using the inline operation input and output syntax +introduced in Smithy 2.0. For example, the following model, + +``` +$version: "2" + +namespace smithy.example + +operation GetFoo { + input := {} + output := {} +} +``` + +Will be equivalent to the following: + +``` +$version: "2" + +namespace smithy.example + +operation GetFoo { + input: GetFooInput + output: GetFooOutput +} + +@input +structure GetFooInput {} + +@output +structure GetFooOutput {} +``` + +#### Additional built-in validation + +To encourage models to utilize the `@input` and `@output` traits, built-in +validation will be added to Smithy. + +When an operation does not define input or output explicitly a WARNING +validation event will be emitted with the ID `OperationMissingInput` or +`OperationMissingOutput`. This can be avoided by explicitly assigning the input +or output of an operation to a shape, including `smithy.api#Unit`. + +If an operation input targets a shape other than `smithy.api#Unit` that is +not marked with the `@input` trait, a WARNING validation event is emitted with +the ID `OperationMissingInputTrait`. A similar event with an ID of +`OperationMissingOutputTrait` is emitted when an operation output targets a +shape that is not `smithy.api#Unit` and is not marked with the `@output` trait. + +``` +operation GetFoo { + input: GetFooInput, // <- OperationMissingInputTrait WARNING + output: GetFooOutput // <- OperationMissingOutputTrait WARNING +} + +structure GetFooInput {} + +structure GetFooOutput {} +``` + +The name of a shape targeted by the `@input` or `@output` trait SHOULD start +with the name of the operation that references it (if any). If not, then a +WARNING is emitted with an ID `OperationInputOutputName`. + +For example, the following model would emit an `OperationInputOutputName` +WARNING because the input shape of the operation does not start with the name +of the operation: + +``` +operation GetFoo { + input: GetFooInput, + output: Foo +} + +@input +structure GetFooInput {} + +@output +structure Foo {} // <- this should be named GetFooOutput +``` + +### The `smithy.api#Unit` shape + +A [unit](https://en.wikipedia.org/wiki/Unit_type) shape will be added to the +Smithy prelude to represent a shape that has no meaningful value. There will +be a single `Unit` shape in Smithy, modeled as a structure with the internal +`@unitType` trait attached to differentiate it from other structures. The +`@internal` trait on the `@unitType` trait ensures that no other unit +structures can be defined. + +The `Unit` shape and `unitType` trait are modeled in the prelude as: + +``` +namespace smithy.api + +/// The single unit type shape, similar to Void and None in other +/// languages, used to represent no meaningful value. +@unitType +structure Unit {} + +/// Specializes a structure as a unit type that has no meaningful value. +/// This trait is private, which ensures that only a single Unit shape +/// can be created, smithy.api#Unit. +@trait(selector: "[id=smithy.api#Unit]") +@internal +structure unitType {} +``` + +`smithy.api#Unit` can only be targeted as the input of an operation, output +of an operation, or from a member of a tagged union. + +* Operations that do not define input or output will target `smithy.api#Unit` + by default, and it is no longer considered a backward compatible change to + change the shape targeted as the input or output of an operation. +* The unit type is also useful for unions. In some cases, the tag of a tagged + union is the only meaningful value that needs to be communicated. In + these cases, the union member can target the `Unit` type to indicate that the + associated value has no meaning. + +While `smithy.api#Unit` is modeled as a specialized structure, code generators +and protocols MAY special-case how a `Unit` is represented. For example, the +following Smithy model: + +``` +union ItemAction { + delete: Unit, + replaceWith: Item +} +``` + +Might be generated in Rust as: + +``` +enum Message { + Delete, + ReplaceWith(Item) +} +``` + +### Other considerations + +#### Add a Smithy transform to generate synthetic input and output shapes + +Because `@input` and `@output` traits are optional, some code generators that +want to define dedicated input and output shapes for every operation will still +want to generate synthetic input and output shapes. To make this easier, +an opt-in model transform will be added to smithy-build to reduce complexity in +Smithy code generators, and centralize the complexity in a single location. + +#### AWS model migration from 1.0 to 2.0 + +Most models created for AWS use dedicated input and output shapes for every +operation. These input and output shapes will be updated to apply the `@input` +and `@output` traits. AWS models that do not define dedicated input and output +shapes (for example, Amazon API Gateway) will be updated to use generated +synthetic input and output shapes marked with the `@input` and `@output` +traits. + +## FAQ + +**Why do we not want to reuse input and output shapes?** + +Every operation should have a dedicated input and output shape that only +functions as the input or output for one operation. The shape should not be +reused by other operations, should not function as both input and output, and +should not be used nested in other members. + +1. Referencing the same input or output structure from multiple operations can + lead to backward-compatibility problems in the future if the inputs or + outputs of the operations ever need to diverge. By using the same structure, + teams unnecessarily tie the interfaces and future evolution of operations + together. +2. Using the same structure for both input and output can lead to + backward-compatibility problems in the future if the members or traits used + in input needs to diverge from those used in output. Reuse between operations + is better facilitated by mixins, which allow members and traits to be shared, + but also allows teams to later refactor or even remove the applied mixins. +3. Referencing an operation input or output shape as a member of another shape + gives the type multiple use cases. This makes it harder for code generators + to give special functionality to input and output types without affecting + the way nested types are handled, leading code generators to generate + synthetic types. + +**How will we get service teams to use the `@input` and `@output` traits?** + +* The inline operation input and output syntax being introduced in Smithy 2.0 + is so convenient it will likely be used by most new models, and it will + automatically add these traits. +* Model validation will emit a WARNING when operations target shapes that do + not have the `@input` or `@output` trait. + +**What happens to operations that have no input or output?** + +* Operations will by default target `smithy.api#Unit`. +* A WARNING will be emitted when an operation defaults to `smithy.api#Unit` + without explicitly targeting `smithy.api#Unit`. This encourages modelers + to be deliberate in their commitment to never needing input or output in + the future for an operation. +* Existing AWS service models will all be updated to define input or output if + they do not already. + +**Will this be a breaking change in the reference Java implementation?** + +No. The existing methods that return things like `Optional.empty` will continue +to function exactly as they did before, but will be marked as deprecated. New +methods will be added that always return a `ShapeId` or `StructureShape` when +accessing things like the input of an operation or querying `OperationIndex`. + +**Won't it be annoying to have to model resource operations that reuse the same members?** + +No. Models typically SHOULD NOT use the same input or output structures for +resources that perform CRUDL operations. + +1. The data returned in a Get operation SHOULD NOT be coupled to the data + returned in a List operation because: + * Getting the details of a resource SHOULD require more elevated permissions + than knowing a resource exists. If List and Get responses are coupled in + the model, sensitive members that might need to be added later to the + output of a Get response will automatically show up in the List response. + * Getting all the details of a resource can be expensive, which impacts + the ability to meet service level agreements around latency when listing + many resources. If List and Get responses are coupled in the model, then + every additional member added to the resource impacts the latency of List + operations multiplied by the number of resources returned. +2. The data returned in a Get response SHOULD NOT be the same data sent in an + update or create input because properties returned in a Get response are + often generated by the server (like `createdAt`). +3. For any case where this _is_ valid overlap, Smithy IDL 2.0 introduces + mixins, which allow for build-time copy and paste. + +**Will this be awkward in AWS SDKs that don't know about unit types?** + +No. Model transformations that convert Smithy models to older AWS modeling +formats will remove operation input and output references to `smithy.api#Unit`. diff --git a/docs/source/1.0/guides/evolving-models.rst b/docs/source/1.0/guides/evolving-models.rst index 593dd170192..bae73a12802 100644 --- a/docs/source/1.0/guides/evolving-models.rst +++ b/docs/source/1.0/guides/evolving-models.rst @@ -30,14 +30,15 @@ Updating operations The following changes to operation shapes are backward-compatible: -#. Changing an operation from no input to referencing a structure that contains - all optional members. -#. Changing an operation from no output to referencing a structure. #. Adding a new error shape if the error shape is only encountered under new conditions that previously released clients/tools will not encounter. The following changes are not backward-compatible: +#. Changing the shape targeted by the input or output of an operation. For + example, it is not backward compatible to change an operation's input or + output from targeting `smithy.api#Unit` to then later target a different + shape. #. Removing or renaming a resource or operation. #. Removing an operation from a service or resource. #. Removing a resource from a service. @@ -56,10 +57,8 @@ Updating structures The following changes to structure shapes are backward-compatible: #. Adding new optional members to a structure. -#. Changing a structure member from :ref:`required-trait` to optional. - The required trait SHOULD only be used for validation purposes; code - generators SHOULD NOT consider whether or not a structure member is - required when generating data structures. +#. Removing the :ref:`required-trait` from a structure member marked with + the :ref:`input-trait`. The following changes to a structure are not backward-compatible: diff --git a/docs/source/1.0/guides/model-linters.rst b/docs/source/1.0/guides/model-linters.rst index 9f5265fb14a..2a5a0309cb3 100644 --- a/docs/source/1.0/guides/model-linters.rst +++ b/docs/source/1.0/guides/model-linters.rst @@ -372,30 +372,6 @@ Default severity ``WARNING`` -.. _InputOutputStructureReuse: - -InputOutputStructureReuse -========================= - -Detects when a structure is used as both input and output or if a structure -is referenced as the input or output for multiple operations. - -Rationale - 1. Using the same structure for both input and output can lead to - backward-compatibility problems in the future if the members or traits - used in input needs to diverge from those used in output. It is always - better to use structures that are exclusively used as input or exclusively - used as output. - 2. Referencing the same input or output structure from multiple operations - can lead to backward-compatibility problems in the future if the - inputs or outputs of the operations ever need to diverge. By using the - same structure, you are unnecessarily tying the interfaces of these - operations together. - -Default severity - ``DANGER`` - - .. _MissingPaginatedTrait: MissingPaginatedTrait diff --git a/docs/source/1.0/spec/aws/aws-cloudformation.rst b/docs/source/1.0/spec/aws/aws-cloudformation.rst index 9ccfd8964ed..7bd28c8c6f5 100644 --- a/docs/source/1.0/spec/aws/aws-cloudformation.rst +++ b/docs/source/1.0/spec/aws/aws-cloudformation.rst @@ -169,43 +169,43 @@ a CloudFormation resource. The following example defines a CloudFormation resource that excludes the ``responseCode`` property: -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - use aws.cloudformation#cfnExcludeProperty - use aws.cloudformation#cfnResource - - @cfnResource - resource Foo { - identifiers: { - fooId: String, - }, - read: GetFoo, - } +.. code-block:: smithy - @readonly - @http(method: "GET", uri: "/foos/{fooId}", code: 200) - operation GetFoo { - input: GetFooRequest, - output: GetFooResponse, - } + namespace smithy.example - structure GetFooRequest { - @httpLabel - @required - fooId: String, - } + use aws.cloudformation#cfnExcludeProperty + use aws.cloudformation#cfnResource - structure GetFooResponse { + @cfnResource + resource Foo { + identifiers: { fooId: String, - - @httpResponseCode - @cfnExcludeProperty - responseCode: Integer, - } + }, + read: GetFoo, + } + + @readonly + @http(method: "GET", uri: "/foos/{fooId}", code: 200) + operation GetFoo { + input: GetFooRequest, + output: GetFooResponse, + } + + @input + structure GetFooRequest { + @httpLabel + @required + fooId: String, + } + + @output + structure GetFooResponse { + fooId: String, + + @httpResponseCode + @cfnExcludeProperty + responseCode: Integer, + } .. _aws-cloudformation-mutability-derivation: @@ -231,74 +231,77 @@ mutability settings: Given the following model without mutability traits applied, -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - namespace smithy.example + namespace smithy.example - use aws.cloudformation#cfnResource + use aws.cloudformation#cfnResource - @cfnResource - resource Foo { - identifiers: { - fooId: String, - }, - create: CreateFoo, - read: GetFoo, - update: UpdateFoo, - } - - operation CreateFoo { - input: CreateFooRequest, - output: CreateFooResponse, - } - - structure CreateFooRequest { - createProperty: ComplexProperty, - mutableProperty: ComplexProperty, - writeProperty: ComplexProperty, - createWriteProperty: ComplexProperty, - } - - structure CreateFooResponse { + @cfnResource + resource Foo { + identifiers: { fooId: String, - } - - @readonly - operation GetFoo { - input: GetFooRequest, - output: GetFooResponse, - } - - structure GetFooRequest { - @required - fooId: String, - } - - structure GetFooResponse { - fooId: String, - createProperty: ComplexProperty, - mutableProperty: ComplexProperty, - readProperty: ComplexProperty, - } - - @idempotent - operation UpdateFoo { - input: UpdateFooRequest, - } - - structure UpdateFooRequest { - @required - fooId: String, - - mutableProperty: ComplexProperty, - writeProperty: ComplexProperty, - } - - structure ComplexProperty { - anotherProperty: String, - } + }, + create: CreateFoo, + read: GetFoo, + update: UpdateFoo, + } + + operation CreateFoo { + input: CreateFooRequest, + output: CreateFooResponse, + } + + @input + structure CreateFooRequest { + createProperty: ComplexProperty, + mutableProperty: ComplexProperty, + writeProperty: ComplexProperty, + createWriteProperty: ComplexProperty, + } + + @output + structure CreateFooResponse { + fooId: String, + } + + @readonly + operation GetFoo { + input: GetFooRequest, + output: GetFooResponse, + } + + @input + structure GetFooRequest { + @required + fooId: String, + } + + @output + structure GetFooResponse { + fooId: String, + createProperty: ComplexProperty, + mutableProperty: ComplexProperty, + readProperty: ComplexProperty, + } + + @idempotent + operation UpdateFoo { + input: UpdateFooRequest, + } + + @input + structure UpdateFooRequest { + @required + fooId: String, + + mutableProperty: ComplexProperty, + writeProperty: ComplexProperty, + } + + structure ComplexProperty { + anotherProperty: String, + } The computed resource property mutabilities are: @@ -390,148 +393,146 @@ mutability trait have the following meanings: The following example defines a CloudFormation resource that marks the ``tags`` and ``barProperty`` properties as fully mutable: -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - namespace smithy.example + namespace smithy.example - use aws.cloudformation#cfnMutability - use aws.cloudformation#cfnResource + use aws.cloudformation#cfnMutability + use aws.cloudformation#cfnResource - @cfnResource(additionalSchemas: [FooProperties]) - resource Foo { - identifiers: { - fooId: String, - }, - create: CreateFoo, - } + @cfnResource(additionalSchemas: [FooProperties]) + resource Foo { + identifiers: { + fooId: String, + }, + create: CreateFoo, + } - operation CreateFoo { - input: CreateFooRequest, - output: CreateFooResponse, - } + operation CreateFoo { + input: CreateFooRequest, + output: CreateFooResponse, + } - structure CreateFooRequest { - @cfnMutability("full") - tags: TagList, - } + @input + structure CreateFooRequest { + @cfnMutability("full") + tags: TagList, + } - structure CreateFooResponse { - fooId: String, - } + @output + structure CreateFooResponse { + fooId: String, + } - structure FooProperties { - @cfnMutability("full") - barProperty: String, - } + structure FooProperties { + @cfnMutability("full") + barProperty: String, + } The following example defines a CloudFormation resource that marks the ``immutableSetting`` property as create and read only: -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - namespace smithy.example + namespace smithy.example - use aws.cloudformation#cfnMutability - use aws.cloudformation#cfnResource + use aws.cloudformation#cfnMutability + use aws.cloudformation#cfnResource - @cfnResource(additionalSchemas: [FooProperties]) - resource Foo { - identifiers: { - fooId: String, - }, - } + @cfnResource(additionalSchemas: [FooProperties]) + resource Foo { + identifiers: { + fooId: String, + }, + } - structure FooProperties { - @cfnMutability("create-and-read") - immutableSetting: Boolean, - } + structure FooProperties { + @cfnMutability("create-and-read") + immutableSetting: Boolean, + } The following example defines a CloudFormation resource that marks the ``updatedAt`` and ``createdAt`` properties as read only: -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy - - namespace smithy.example + namespace smithy.example - use aws.cloudformation#cfnMutability - use aws.cloudformation#cfnResource + use aws.cloudformation#cfnMutability + use aws.cloudformation#cfnResource - @cfnResource(additionalSchemas: [FooProperties]) - resource Foo { - identifiers: { - fooId: String, - }, - read: GetFoo, - } - - @readonly - operation GetFoo { - input: GetFooRequest, - output: GetFooResponse, - } - - structure GetFooRequest { - @required - fooId: String - } - - structure GetFooResponse { - @cfnMutability("read") - updatedAt: Timestamp, - } - - structure FooProperties { - @cfnMutability("read") - createdAt: Timestamp, - } + @cfnResource(additionalSchemas: [FooProperties]) + resource Foo { + identifiers: { + fooId: String, + }, + read: GetFoo, + } + + @readonly + operation GetFoo { + input: GetFooRequest, + output: GetFooResponse, + } + + @input + structure GetFooRequest { + @required + fooId: String + } + + @output + structure GetFooResponse { + @cfnMutability("read") + updatedAt: Timestamp, + } + + structure FooProperties { + @cfnMutability("read") + createdAt: Timestamp, + } The following example defines a CloudFormation resource that marks the derivable ``secret`` and ``password`` properties as write only: -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - namespace smithy.example + namespace smithy.example - use aws.cloudformation#cfnMutability - use aws.cloudformation#cfnResource + use aws.cloudformation#cfnMutability + use aws.cloudformation#cfnResource - @cfnResource(additionalSchemas: [FooProperties]) - resource Foo { - identifiers: { - fooId: String, - }, - create: CreateFoo, - } + @cfnResource(additionalSchemas: [FooProperties]) + resource Foo { + identifiers: { + fooId: String, + }, + create: CreateFoo, + } - operation CreateFoo { - input: CreateFooRequest, - output: CreateFooResponse, - } + operation CreateFoo { + input: CreateFooRequest, + output: CreateFooResponse, + } - structure CreateFooRequest { - @cfnMutability("write") - secret: String, - } + @input + structure CreateFooRequest { + @cfnMutability("write") + secret: String, + } - structure CreateFooResponse { - fooId: String, - } + @output + structure CreateFooResponse { + fooId: String, + } - structure FooProperties { - @cfnMutability("write") - password: String, - } + structure FooProperties { + @cfnMutability("write") + password: String, + } .. smithy-trait:: aws.cloudformation#cfnName @@ -555,20 +556,18 @@ Value type Given the following structure definition: -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - namespace smithy.example + namespace smithy.example - use aws.cloudformation#cfnName + use aws.cloudformation#cfnName - structure AdditionalFooProperties { - bar: String, + structure AdditionalFooProperties { + bar: String, - @cfnName("Tags") - tagList: TagList, - } + @cfnName("Tags") + tagList: TagList, + } the following property names are derived from it: @@ -612,35 +611,34 @@ input to an operation bound to the ``read`` lifecycle of a resource. The following example defines a CloudFormation resource that has the ``fooAlias`` property as an additional identifier: -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - namespace smithy.example + namespace smithy.example - use aws.cloudformation#cfnAdditionalIdentifier - use aws.cloudformation#cfnResource + use aws.cloudformation#cfnAdditionalIdentifier + use aws.cloudformation#cfnResource - @cfnResource - resource Foo { - identifiers: { - fooId: String, - }, - read: GetFoo, - } + @cfnResource + resource Foo { + identifiers: { + fooId: String, + }, + read: GetFoo, + } - @readonly - operation GetFoo { - input: GetFooRequest, - } + @readonly + operation GetFoo { + input: GetFooRequest, + } - structure GetFooRequest { - @required - fooId: String, + @input + structure GetFooRequest { + @required + fooId: String, - @cfnAdditionalIdentifier - fooAlias: String, - } + @cfnAdditionalIdentifier + fooAlias: String, + } ------------- @@ -654,125 +652,128 @@ can be annotated for CloudFormation resource generation. Given the following model, -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - use aws.cloudformation#cfnAdditionalIdentifier - use aws.cloudformation#cfnExcludeProperty - use aws.cloudformation#cfnMutability - use aws.cloudformation#cfnResource - - @cfnResource(additionalSchemas: [FooProperties]) - resource Foo { - identifiers: { - fooId: String, - }, - create: CreateFoo, - read: GetFoo, - update: UpdateFoo, - } - - @http(method: "POST", uri: "/foos", code: 200) - operation CreateFoo { - input: CreateFooRequest, - output: CreateFooResponse, - } - - structure CreateFooRequest { - @cfnMutability("full") - tags: TagList, - - @cfnMutability("write") - secret: String, - - fooAlias: String, - - createProperty: ComplexProperty, - mutableProperty: ComplexProperty, - writeProperty: ComplexProperty, - createWriteProperty: ComplexProperty, - } - - structure CreateFooResponse { - fooId: String, - } - - @readonly - @http(method: "GET", uri: "/foos/{fooId}", code: 200) - operation GetFoo { - input: GetFooRequest, - output: GetFooResponse, - } +.. code-block:: smithy - structure GetFooRequest { - @httpLabel - @required - fooId: String, - - @httpQuery("fooAlias") - @cfnAdditionalIdentifier - fooAlias: String, - } - - structure GetFooResponse { - fooId: String, - - @httpResponseCode - @cfnExcludeProperty - responseCode: Integer, + namespace smithy.example - @cfnMutability("read") - updatedAt: Timestamp, + use aws.cloudformation#cfnAdditionalIdentifier + use aws.cloudformation#cfnExcludeProperty + use aws.cloudformation#cfnMutability + use aws.cloudformation#cfnResource - fooAlias: String, - createProperty: ComplexProperty, - mutableProperty: ComplexProperty, - readProperty: ComplexProperty, - } - - @idempotent - @http(method: "PUT", uri: "/foos/{fooId}", code: 200) - operation UpdateFoo { - input: UpdateFooRequest, - } - - structure UpdateFooRequest { - @httpLabel - @required + @cfnResource(additionalSchemas: [FooProperties]) + resource Foo { + identifiers: { fooId: String, - - fooAlias: String, - mutableProperty: ComplexProperty, - writeProperty: ComplexProperty, - } - - structure FooProperties { - addedProperty: String, - - @cfnMutability("full") - barProperty: String, - - @cfnName("Immutable") - @cfnMutability("create-and-read") - immutableSetting: Boolean, - - @cfnMutability("read") - createdAt: Timestamp, - - @cfnMutability("write") - password: String, - } - - structure ComplexProperty { - anotherProperty: String, - } - - list TagList { - member: String - } + }, + create: CreateFoo, + read: GetFoo, + update: UpdateFoo, + } + + @http(method: "POST", uri: "/foos", code: 200) + operation CreateFoo { + input: CreateFooRequest, + output: CreateFooResponse, + } + + @input + structure CreateFooRequest { + @cfnMutability("full") + tags: TagList, + + @cfnMutability("write") + secret: String, + + fooAlias: String, + + createProperty: ComplexProperty, + mutableProperty: ComplexProperty, + writeProperty: ComplexProperty, + createWriteProperty: ComplexProperty, + } + + @output + structure CreateFooResponse { + fooId: String, + } + + @readonly + @http(method: "GET", uri: "/foos/{fooId}", code: 200) + operation GetFoo { + input: GetFooRequest, + output: GetFooResponse, + } + + @input + structure GetFooRequest { + @httpLabel + @required + fooId: String, + + @httpQuery("fooAlias") + @cfnAdditionalIdentifier + fooAlias: String, + } + + @output + structure GetFooResponse { + fooId: String, + + @httpResponseCode + @cfnExcludeProperty + responseCode: Integer, + + @cfnMutability("read") + updatedAt: Timestamp, + + fooAlias: String, + createProperty: ComplexProperty, + mutableProperty: ComplexProperty, + readProperty: ComplexProperty, + } + + @idempotent + @http(method: "PUT", uri: "/foos/{fooId}", code: 200) + operation UpdateFoo { + input: UpdateFooRequest, + } + + @input + structure UpdateFooRequest { + @httpLabel + @required + fooId: String, + + fooAlias: String, + mutableProperty: ComplexProperty, + writeProperty: ComplexProperty, + } + + structure FooProperties { + addedProperty: String, + + @cfnMutability("full") + barProperty: String, + + @cfnName("Immutable") + @cfnMutability("create-and-read") + immutableSetting: Boolean, + + @cfnMutability("read") + createdAt: Timestamp, + + @cfnMutability("write") + password: String, + } + + structure ComplexProperty { + anotherProperty: String, + } + + list TagList { + member: String + } The following CloudFormation resource information is computed: diff --git a/docs/source/1.0/spec/aws/aws-core.rst b/docs/source/1.0/spec/aws/aws-core.rst index 51d9d63ae09..d86c61f7440 100644 --- a/docs/source/1.0/spec/aws/aws-core.rst +++ b/docs/source/1.0/spec/aws/aws-core.rst @@ -973,193 +973,69 @@ The following model illustrates an API that uses a ``DescribeEndpoints`` operation to perform endpoint discovery for a ``GetObject`` operation using an ``clientEndpointDiscoveryId``. -.. tabs:: - - .. code-tab:: smithy - - @aws.api#clientEndpointDiscovery( - operation: DescribeEndpoints, - error: InvalidEndpointError, - ) - service FooService { - version: "2019-09-10", - operations: [DescribeEndpoints, GetObject] - } - - operation DescribeEndpoints { - input: DescribeEndpointsInput, - output: DescribeEndpointsOutput, - errors: [InvalidEndpointError] - } +.. code-block:: smithy - @error("client") - @httpError(421) - structure InvalidEndpointError {} + @aws.api#clientEndpointDiscovery( + operation: DescribeEndpoints, + error: InvalidEndpointError, + ) + service FooService { + version: "2019-09-10", + operations: [DescribeEndpoints, GetObject] + } - structure DescribeEndpointsInput { - Operation: String, - Identifiers: Identifiers, - } + operation DescribeEndpoints { + input: DescribeEndpointsInput, + output: DescribeEndpointsOutput, + errors: [InvalidEndpointError] + } - map Identifiers { - key: String, - value: String - } + @error("client") + @httpError(421) + structure InvalidEndpointError {} - structure DescribeEndpointsOutput { - Endpoints: Endpoints, - } + @input + structure DescribeEndpointsInput { + Operation: String, + Identifiers: Identifiers, + } - list Endpoints { - member: Endpoint - } + map Identifiers { + key: String, + value: String + } - structure Endpoint { - Address: String, - CachePeriodInMinutes: Long, - } + @output + structure DescribeEndpointsOutput { + Endpoints: Endpoints, + } - @aws.api#clientDiscoveredEndpoint(required: true) - operation GetObject { - input: GetObjectInput, - output: GetObjectOutput - } + list Endpoints { + member: Endpoint + } - structure GetObjectInput { - @clientEndpointDiscoveryId - @required - Id: String, - } + structure Endpoint { + Address: String, + CachePeriodInMinutes: Long, + } - structure GetObjectOutput { - Object: Blob, - } + @aws.api#clientDiscoveredEndpoint(required: true) + operation GetObject { + input: GetObjectInput, + output: GetObjectOutput + } - .. code-tab:: json + @input + structure GetObjectInput { + @clientEndpointDiscoveryId + @required + Id: String, + } - { - "smithy": "1.0", - "shapes": { - "ns.foo#FooService": { - "type": "service", - "version": "2019-09-10", - "operations": [ - { - "target": "ns.foo#DescribeEndpoints" - }, - { - "target": "ns.foo#GetObject" - } - ], - "traits": { - "aws.api#clientEndpointDiscovery": { - "operation": "ns.foo#DescribeEndpoints", - "error": "InvalidEndpointError" - } - } - }, - "ns.foo#DescribeEndpoints": { - "type": "operation", - "input": { - "target": "ns.foo#DescribeEndpointsInput" - }, - "output": { - "target": "ns.foo#DescribeEndpointsOutput" - } - }, - "ns.foo#DescribeEndpointsInput": { - "type": "structure", - "members": { - "Operation": { - "target": "smithy.api#String" - }, - "Identifiers": { - "target": "ns.foo#Identifiers" - } - } - }, - "ns.foo#Identifiers": { - "type": "map", - "key": { - "target": "smithy.api#String" - }, - "value": { - "target": "smithy.api#String" - } - }, - "ns.foo#DescribeEndpointsOutput": { - "type": "structure", - "members": { - "Endpoints": { - "target": "ns.foo#Endpoints" - } - } - }, - "ns.foo#Endpoints": { - "type": "list", - "member": { - "target": "ns.foo#Endpoint" - } - }, - "ns.foo#Endpoint": { - "type": "structure", - "members": { - "Address": { - "target": "smithy.api#String" - }, - "CachePeriodInMinutes": { - "target": "smithy.api#Long" - } - } - }, - "ns.foo#GetObject": { - "type": "operation", - "input": { - "target": "ns.foo#GetObjectInput" - }, - "output": { - "target": "ns.foo#GetObjectOutput" - }, - "errors": [ - { - "target": "ns.foo#InvalidEndpointError" - } - ], - "traits": { - "aws.api#clientDiscoveredEndpoint": { - "required": true - } - } - }, - "ns.foo#GetObjectInput": { - "type": "structure", - "members": { - "Id": { - "target": "smithy.api#String", - "traits": { - "aws.api#clientEndpointDiscoveryId": {}, - "smithy.api#required": {} - } - } - } - }, - "ns.foo#GetObjectOutput": { - "type": "structure", - "members": { - "Object": { - "target": "smithy.api#Blob" - } - } - }, - "ns.foo#InvalidEndpointError": { - "type": "structure", - "traits": { - "smithy.api#error": "client", - "smithy.api#httpError": 421 - } - } - } - } + @output + structure GetObjectOutput { + Object: Blob, + } Client Behavior diff --git a/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst b/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst index 6b492ca77eb..0628d394fcc 100644 --- a/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst @@ -244,6 +244,7 @@ For example, given the following: .. code-block:: smithy + @input structure Ec2QueryStructuresInput { foo: String, @@ -286,6 +287,7 @@ For example, given the following: .. code-block:: smithy + @input structure Ec2QueryListsInput { ListArg: StringList, ComplexListArg: GreetingList, diff --git a/docs/source/1.0/spec/aws/aws-query-protocol.rst b/docs/source/1.0/spec/aws/aws-query-protocol.rst index 3a46cac6482..df784f0b435 100644 --- a/docs/source/1.0/spec/aws/aws-query-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-query-protocol.rst @@ -168,6 +168,7 @@ For example, given the following: .. code-block:: smithy + @input structure QueryStructuresInput { foo: String, @@ -206,6 +207,7 @@ For example, given the following: .. code-block:: smithy + @input structure QueryListsInput { ListArg: StringList, ComplexListArg: GreetingList, @@ -270,6 +272,7 @@ For example, given the following: .. code-block:: smithy + @input structure QueryMapsInput { MapArg: StringMap, diff --git a/docs/source/1.0/spec/aws/customizations/s3-customizations.rst b/docs/source/1.0/spec/aws/customizations/s3-customizations.rst index 98e0edb1915..c6754472197 100644 --- a/docs/source/1.0/spec/aws/customizations/s3-customizations.rst +++ b/docs/source/1.0/spec/aws/customizations/s3-customizations.rst @@ -148,70 +148,27 @@ Value type Consider the following *abridged* model of S3's ``GetBucketLocation`` operation: -.. tabs:: - - .. code-tab:: smithy - - use aws.customizations#s3UnwrappedXmlOutput - - @enum([ - { value: "us-west-2", name: "us_west_2" } - ]) - string BucketLocationConstraint - - @xmlName("LocationConstraint") - structure GetBucketLocationOutput { - LocationConstraint: BucketLocationConstraint, - } - - @http(uri: "/GetBucketLocation", method: "GET") - @s3UnwrappedXmlOutput - operation GetBucketLocation { - output: GetBucketLocationOutput, - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#BucketLocationConstraint": { - "type": "string", - "traits": { - "smithy.api#enum": [ - { - "value": "us-west-2", - "name": "us_west_2" - } - ] - } - }, - "smithy.example#GetBucketLocationOutput": { - "type": "structure", - "members": { - "LocationConstraint": { - "target": "smithy.example#BucketLocationConstraint" - } - }, - "traits": { - "smithy.api#xmlName": "LocationConstraint" - } - }, - "smithy.example#GetBucketLocation": { - "type": "operation", - "output": { - "target": "smithy.example#GetBucketLocationOutput" - }, - "traits": { - "smithy.api#http": { - "uri": "/GetBucketLocation", - "method": "GET" - }, - "aws.customizations#s3UnwrappedXmlOutput": {} - } - } - } - } +.. code-block:: smithy + + use aws.customizations#s3UnwrappedXmlOutput + + @http(uri: "/GetBucketLocation", method: "GET") + @s3UnwrappedXmlOutput + operation GetBucketLocation { + input: GetBucketLocationInput, + output: GetBucketLocationOutput + } + + @output + @xmlName("LocationConstraint") + structure GetBucketLocationOutput { + LocationConstraint: BucketLocationConstraint + } + + @enum([ + { value: "us-west-2", name: "us_west_2" } + ]) + string BucketLocationConstraint Since this operation is modeled with ``@s3UnwrappedXmlOutput``, an Amazon S3 client should expect the response from S3 to be unwrapped as shown below: diff --git a/docs/source/1.0/spec/core/behavior-traits.rst b/docs/source/1.0/spec/core/behavior-traits.rst index f7db0d52467..ea68367fee2 100644 --- a/docs/source/1.0/spec/core/behavior-traits.rst +++ b/docs/source/1.0/spec/core/behavior-traits.rst @@ -42,18 +42,17 @@ provided request token to identify and discard duplicate requests. Client implementations MAY automatically provide a value for a request token member if and only if the member is not explicitly provided. -.. tabs:: +.. code-block:: - .. code-tab:: smithy + operation AllocateWidget { + input: AllocateWidgetInput + } - operation AllocateWidget { - input: AllocateWidgetInput - } - - structure AllocateWidgetInput { - @idempotencyToken - clientToken: String, - } + @input + structure AllocateWidgetInput { + @idempotencyToken + clientToken: String, + } .. smithy-trait:: smithy.api#idempotent @@ -74,15 +73,13 @@ Value type Conflicts with :ref:`readonly-trait` -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - @idempotent - operation DeleteSomething { - input: DeleteSomethingInput, - output: DeleteSomethingOutput - } + @idempotent + operation DeleteSomething { + input: DeleteSomethingInput, + output: DeleteSomethingOutput + } .. note:: @@ -106,15 +103,13 @@ Value type Conflicts with :ref:`idempotent-trait` -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy - - @readonly - operation GetSomething { - input: GetSomethingInput, - output: GetSomethingOutput - } + @readonly + operation GetSomething { + input: GetSomethingInput, + output: GetSomethingOutput + } .. smithy-trait:: smithy.api#retryable @@ -146,19 +141,17 @@ The retryable trait is a structure that contains the following members: - ``boolean`` - Indicates that the error is a retryable throttling error. -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy - - @error("server") - @retryable - @httpError(503) - structure ServiceUnavailableError {} + @error("server") + @retryable + @httpError(503) + structure ServiceUnavailableError {} - @error("client") - @retryable(throttling: true) - @httpError(429) - structure ThrottlingError {} + @error("client") + @retryable(throttling: true) + @httpError(429) + structure ThrottlingError {} .. _pagination: @@ -239,92 +232,35 @@ The ``paginated`` trait is a structure that contains the following members: The following example defines a paginated operation that sets each value explicitly on the operation. -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy + namespace smithy.example - namespace smithy.example + @readonly + @paginated(inputToken: "nextToken", outputToken: "nextToken", + pageSize: "maxResults", items: "foos") + operation GetFoos { + input: GetFoosInput, + output: GetFoosOutput + } - @readonly - @paginated(inputToken: "nextToken", outputToken: "nextToken", - pageSize: "maxResults", items: "foos") - operation GetFoos { - input: GetFoosInput, - output: GetFoosOutput - } + @input + structure GetFoosInput { + maxResults: Integer, + nextToken: String + } - structure GetFoosInput { - maxResults: Integer, - nextToken: String - } - - structure GetFoosOutput { - nextToken: String, + @output + structure GetFoosOutput { + nextToken: String, - @required - foos: StringList, - } + @required + foos: StringList, + } - list StringList { - member: String - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#GetFoos": { - "type": "operation", - "input": { - "target": "smithy.example#GetFoosInput" - }, - "output": { - "target": "smithy.example#GetFoosOutput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#paginated": { - "inputToken": "nextToken", - "outputToken": "nextToken", - "pageSize": "maxResults", - "items": "foos" - } - } - }, - "smithy.example#GetFoosInput": { - "type": "structure", - "members": { - "maxResults": { - "target": "smithy.api#Integer" - }, - "nextToken": { - "target": "smithy.api#String" - } - } - }, - "smithy.example#GetFoosOutput": { - "type": "structure", - "members": { - "nextToken": { - "target": "smithy.api#String" - }, - "foos": { - "target": "smithy.example#StringList", - "traits": { - "smithy.api#required": {} - } - } - } - }, - "smithy.example#StringList": { - "type": "list", - "member": { - "target": "smithy.api#String" - } - } - } - } + list StringList { + member: String + } Attaching the ``paginated`` trait to a service provides default pagination configuration settings to all operations bound within the closure of the @@ -400,108 +336,41 @@ following ABNF. The following example defines a paginated operation which uses a result wrapper where the output token and items are referenced by paths. -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy + namespace smithy.example - namespace smithy.example + @readonly + @paginated(inputToken: "nextToken", outputToken: "result.nextToken", + pageSize: "maxResults", items: "result.foos") + operation GetFoos { + input: GetFoosInput, + output: GetFoosOutput + } - @readonly - @paginated(inputToken: "nextToken", outputToken: "result.nextToken", - pageSize: "maxResults", items: "result.foos") - operation GetFoos { - input: GetFoosInput, - output: GetFoosOutput - } + @input + structure GetFoosInput { + maxResults: Integer, + nextToken: String + } - structure GetFoosInput { - maxResults: Integer, - nextToken: String - } + @output + structure GetFoosOutput { + @required + result: ResultWrapper + } - structure GetFoosOutput { - @required - result: ResultWrapper - } + structure ResultWrapper { + nextToken: String, - structure ResultWrapper { - nextToken: String, + @required + foos: StringList, + } - @required - foos: StringList, - } + list StringList { + member: String + } - list StringList { - member: String - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#GetFoos": { - "type": "operation", - "input": { - "target": "smithy.example#GetFoosInput" - }, - "output": { - "target": "smithy.example#GetFoosOutput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#paginated": { - "inputToken": "nextToken", - "outputToken": "result.nextToken", - "pageSize": "maxResults", - "items": "result.foos" - } - } - }, - "smithy.example#GetFoosInput": { - "type": "structure", - "members": { - "maxResults": { - "target": "smithy.api#Integer" - }, - "nextToken": { - "target": "smithy.api#String" - } - } - }, - "smithy.example#GetFoosOutput": { - "type": "structure", - "members": { - "result": { - "target": "smithy.example#ResultWrapper", - "traits": { - "smithy.api#required": {} - } - } - } - }, - "smithy.example#ResultWrapper": { - "type": "structure", - "members": { - "nextToken": { - "target": "smithy.api#String" - }, - "foos": { - "target": "smithy.example#StringList", - "traits": { - "smithy.api#required": {} - } - } - } - }, - "smithy.example#StringList": { - "type": "list", - "member": { - "target": "smithy.api#String" - } - } - } - } Pagination Behavior =================== diff --git a/docs/source/1.0/spec/core/documentation-traits.rst b/docs/source/1.0/spec/core/documentation-traits.rst index 79eefbb786b..bd6f9d2e192 100644 --- a/docs/source/1.0/spec/core/documentation-traits.rst +++ b/docs/source/1.0/spec/core/documentation-traits.rst @@ -375,17 +375,16 @@ Value type Conflicts with :ref:`required-trait` -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - structure PutContentsInput { - @required - contents: String, + @input + structure PutContentsInput { + @required + contents: String, - @recommended(reason: "Validation will reject contents if they are invalid.") - validateContents: Boolean, - } + @recommended(reason: "Validation will reject contents if they are invalid.") + validateContents: Boolean, + } .. smithy-trait:: smithy.api#sensitive diff --git a/docs/source/1.0/spec/core/endpoint-traits.rst b/docs/source/1.0/spec/core/endpoint-traits.rst index 33bb8e26bdb..d7ae1462420 100644 --- a/docs/source/1.0/spec/core/endpoint-traits.rst +++ b/docs/source/1.0/spec/core/endpoint-traits.rst @@ -53,59 +53,24 @@ The ``endpoint`` trait is a structure that contains the following members: The following example defines an operation that uses a custom endpoint: -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy + namespace smithy.example - namespace smithy.example + @readonly + @endpoint(hostPrefix: "{foo}.data.") + operation GetStatus { + input: GetStatusInput, + output: GetStatusOutput + } - @readonly - @endpoint(hostPrefix: "{foo}.data.") - operation GetStatus { - input: GetStatusInput, - output: GetStatusOutput - } + @input + structure GetStatusInput { + @required + @hostLabel + foo: String + } - structure GetStatusInput { - @required - @hostLabel - foo: String - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#GetStatus": { - "type": "operation", - "input": { - "target": "smithy.example#GetStatusInput" - }, - "output": { - "target": "smithy.example#GetStatusOutput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#endpoint": { - "hostPrefix": "{foo}.data." - } - } - }, - "smithy.example#GetStatusInput": { - "type": "structure", - "members": { - "foo": { - "target": "smithy.api#String", - "traits": { - "smithy.api#required": {}, - "smithy.api#hostLabel": {} - } - } - } - } - } - } .. _endpoint-Labels: @@ -121,57 +86,21 @@ be marked as :ref:`required `, the input member MUST have the Given the following operation, -.. tabs:: - - .. code-tab:: smithy - - @readonly - @endpoint(hostPrefix: "{foo}.data.") - operation GetStatus { - input: GetStatusInput, - output: GetStatusOutput - } - - structure GetStatusInput { - @required - @hostLabel - foo: String - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#GetStatus": { - "type": "operation", - "input": { - "target": "smithy.example#GetStatusInput" - }, - "output": { - "target": "smithy.example#GetStatusOutput" - }, - "traits": { - "smithy.api#readonly": {}, - "smithy.api#endpoint": { - "hostPrefix": "{foo}.data." - } - } - }, - "smithy.example#GetStatusInput": { - "type": "structure", - "members": { - "foo": { - "target": "smithy.api#String", - "traits": { - "smithy.api#required": {}, - "smithy.api#hostLabel": {} - } - } - } - } - } - } +.. code-block:: smithy + + @readonly + @endpoint(hostPrefix: "{foo}.data.") + operation GetStatus { + input: GetStatusInput, + output: GetStatusOutput + } + + @input + structure GetStatusInput { + @required + @hostLabel + foo: String + } and the following value provided for ``GetStatusInput``, diff --git a/docs/source/1.0/spec/core/http-traits.rst b/docs/source/1.0/spec/core/http-traits.rst index ba71de70d24..98ba9a6aaca 100644 --- a/docs/source/1.0/spec/core/http-traits.rst +++ b/docs/source/1.0/spec/core/http-traits.rst @@ -54,43 +54,42 @@ The ``http`` trait is a structure that supports the following members: The following example defines an operation that uses HTTP bindings: -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - namespace smithy.example + namespace smithy.example - @idempotent - @http(method: "PUT", uri: "/{bucketName}/{key}", code: 200) - operation PutObject { - input: PutObjectInput - } + @idempotent + @http(method: "PUT", uri: "/{bucketName}/{key}", code: 200) + operation PutObject { + input: PutObjectInput + } - structure PutObjectInput { - // Sent in the URI label named "key". - @required - @httpLabel - key: ObjectKey, + @input + structure PutObjectInput { + // Sent in the URI label named "key". + @required + @httpLabel + key: ObjectKey, - // Sent in the URI label named "bucketName". - @required - @httpLabel - bucketName: String, + // Sent in the URI label named "bucketName". + @required + @httpLabel + bucketName: String, - // Sent in the X-Foo header - @httpHeader("X-Foo") - foo: String, + // Sent in the X-Foo header + @httpHeader("X-Foo") + foo: String, - // Sent in the query string as paramName - @httpQuery("paramName") - someValue: String, + // Sent in the query string as paramName + @httpQuery("paramName") + someValue: String, - // Sent in the body - data: MyBlob, + // Sent in the body + data: MyBlob, - // Sent in the body - additional: String, - } + // Sent in the body + additional: String, + } .. _http-method: @@ -137,15 +136,13 @@ constraints: #. MUST NOT case-sensitively conflict with other ``http`` / ``uri`` properties. -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - @readonly - @http(method: "GET", uri: "/foo/{baz}") - operation GetService { - output: GetServiceOutput - } + @readonly + @http(method: "GET", uri: "/foo/{baz}") + operation GetService { + output: GetServiceOutput + } Literal character sequences @@ -483,13 +480,11 @@ Value type The following example defines an error with an HTTP status code of ``404``. -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - @error("client") - @httpError(404) - structure MyError {} + @error("client") + @httpError(404) + structure MyError {} .. rubric:: Default HTTP status codes @@ -634,24 +629,23 @@ Conflicts with The following example defines an operation that send an HTTP label named ``foo`` as part of the URI of an HTTP request: -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - namespace smithy.example + namespace smithy.example - @readonly - @http(method: "GET", uri: "/{foo}") - operation GetStatus { - input: GetStatusInput, - output: GetStatusOutput - } + @readonly + @http(method: "GET", uri: "/{foo}") + operation GetStatus { + input: GetStatusInput, + output: GetStatusOutput + } - structure GetStatusInput { - @required - @httpLabel - foo: String - } + @input + structure GetStatusInput { + @required + @httpLabel + foo: String + } .. rubric:: Relationship to :ref:`http-trait` @@ -721,26 +715,29 @@ Structurally exclusive The following example defines an operation that returns a ``blob`` of binary data in a response: -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy + namespace smithy.example - namespace smithy.example + @readonly + @http(method: "GET", uri: "/random-binary-data") + operation GetRandomBinaryData { + input: GetRandomBinaryDataInput, + output: GetRandomBinaryDataOutput + } - @readonly - @http(method: "GET", uri: "/random-binary-data") - operation GetRandomBinaryData { - output: GetRandomBinaryDataOutput, - } + @input + structure GetRandomBinaryDataInput {} - structure GetRandomBinaryDataOutput { - @required - @httpHeader("Content-Type") - contentType: String, + @output + structure GetRandomBinaryDataOutput { + @required + @httpHeader("Content-Type") + contentType: String, - @httpPayload - content: Blob, - } + @httpPayload + content: Blob, + } .. _http-protocol-document-payloads: @@ -807,25 +804,24 @@ Structurally exclusive Given the following Smithy model: -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - @readonly - @http(method: "GET", uri: "/myOperation") - operation MyOperation { - input: MyOperationInput - } + @readonly + @http(method: "GET", uri: "/myOperation") + operation MyOperation { + input: MyOperationInput + } - structure MyOperationInput { - @httpPrefixHeaders("X-Foo-") - headers: StringMap - } + @input + structure MyOperationInput { + @httpPrefixHeaders("X-Foo-") + headers: StringMap + } - map StringMap { - key: String, - value: String - } + map StringMap { + key: String, + value: String + } And given the following input to ``MyOperation``: @@ -887,27 +883,26 @@ The following example defines an operation that optionally sends the ``color``, ``shape``, and ``size`` query string parameters in an HTTP request: -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - @readonly - @http(method: "GET", uri: "/things") - operation ListThings { - input: ListThingsInput, - output: ListThingsOutput, // omitted for brevity - } + @readonly + @http(method: "GET", uri: "/things") + operation ListThings { + input: ListThingsInput, + output: ListThingsOutput, // omitted for brevity + } - structure ListThingsInput { - @httpQuery("color") - color: String, + @input + structure ListThingsInput { + @httpQuery("color") + color: String, - @httpQuery("shape") - shape: String, + @httpQuery("shape") + shape: String, - @httpQuery("size") - size: Integer, - } + @httpQuery("size") + size: Integer, + } .. rubric:: Serialization rules @@ -984,26 +979,25 @@ Structurally exclusive The following example defines an operation that optionally sends the target input map as query string parameters in an HTTP request: -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - @readonly - @http(method: "GET", uri: "/things") - operation ListThings { - input: ListThingsInput, - output: ListThingsOutput, // omitted for brevity - } + @readonly + @http(method: "GET", uri: "/things") + operation ListThings { + input: ListThingsInput, + output: ListThingsOutput, // omitted for brevity + } - structure ListThingsInput { - @httpQueryParams() - myParams: MapOfStrings, - } + @input + structure ListThingsInput { + @httpQueryParams() + myParams: MapOfStrings, + } - map MapOfStrings { - key: String, - value: String - } + map MapOfStrings { + key: String, + value: String + } .. rubric:: ``httpQueryParams`` is only used on input @@ -1025,28 +1019,27 @@ If a member with the ``httpQueryParams`` trait and a member with the :ref:`httpQ conflict, clients MUST use the value set by the member with the :ref:`httpQuery-trait` and disregard the value set by ``httpQueryParams``. For example, given the following model: -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - @http(method: "POST", uri: "/things") - operation PutThing { - input: PutThingInput - } + @http(method: "POST", uri: "/things") + operation PutThing { + input: PutThingInput + } - structure PutThingInput { - @httpQuery - @required - thingId: String, + @input + structure PutThingInput { + @httpQuery + @required + thingId: String, - @httpQueryParams - tags: MapOfStrings - } + @httpQueryParams + tags: MapOfStrings + } - map MapOfStrings { - key: String, - value: String - } + map MapOfStrings { + key: String, + value: String + } And given the following input to ``PutThing``: @@ -1270,77 +1263,29 @@ output member that targets a shape marked with the :ref:`streaming-trait`. The following example defines an operation that uses an input event stream and HTTP bindings: -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example +.. code-block:: smithy - @http(method: "POST", uri: "/messages") - operation PublishMessages { - input: PublishMessagesInput - } + namespace smithy.example - structure PublishMessagesInput { - @httpPayload - messages: MessageStream, - } + @http(method: "POST", uri: "/messages") + operation PublishMessages { + input: PublishMessagesInput + } - @streaming - union MessageStream { - message: Message, - } + @input + structure PublishMessagesInput { + @httpPayload + messages: MessageStream, + } - structure Message { - message: String, - } + @streaming + union MessageStream { + message: Message, + } - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#PublishMessages": { - "type": "operation", - "input": { - "target": "smithy.example#PublishMessagesInput" - }, - "traits": { - "smithy.api#http": { - "uri": "/messages", - "method": "POST" - } - } - }, - "smithy.example#PublishMessagesInput": { - "type": "structure", - "members": { - "messages": { - "target": "smithy.example#MessageStream", - "traits": { - "smithy.api#httpPayload": {} - } - } - } - }, - "smithy.example#MessageStream": { - "type": "union", - "members": { - "message": { - "target": "smithy.example#Message" - } - } - }, - "smithy.example#Message": { - "type": "structure", - "members": { - "message": { - "target": "smithy.api#String" - } - } - } - } - } + structure Message { + message: String, + } The following is **invalid** because the operation has the ``http`` trait and an input member is marked with the ``streaming`` trait but not @@ -1355,6 +1300,7 @@ marked with the ``httpPayload`` trait: input: InvalidOperationInput } + @input structure InvalidOperationInput { invalid: MessageStream, // <-- Missing the @httpPayload trait } diff --git a/docs/source/1.0/spec/core/model.rst b/docs/source/1.0/spec/core/model.rst index bad7fc8eeb0..23252ee3567 100644 --- a/docs/source/1.0/spec/core/model.rst +++ b/docs/source/1.0/spec/core/model.rst @@ -179,8 +179,8 @@ Shapes are visualized using the following diagram: │timestamp │──┤ │ ├────────────┤ │ ┌─────────────────────────┐ └──────────┘ │ │ │member │ │ │ Operation │ │ │ └────────────┘ │ ├─────────────────────────┤ - ┌───────────────┐ │ ┌────────────┐ │ │input: Structure? │ - │ «abstract» │ ├────│ Map │ ├────│output: Structure? │ + ┌───────────────┐ │ ┌────────────┐ │ │input: Structure │ + │ «abstract» │ ├────│ Map │ ├────│output: Structure │ │ Number │ │ ├────────────┤ │ │errors: [Structure]? │ └───────────────┘ │ │key │ │ └─────────────────────────┘ △ │ │value │ │ ┌─────────────────────────┐ @@ -815,10 +815,13 @@ Union The union type represents a `tagged union data structure`_ that can take on several different, but fixed, types. Unions function similarly to -structures except that only one member can be used at any one time. A union -shape MUST contain one or more named :ref:`members `. Unions are -defined in the IDL using a :ref:`union_statement `. +structures except that only one member can be used at any one time. Each +member in the union is a variant of the tagged union, where member names +are the tags of each variant, and the shapes targeted by members are the +values of each variant. +Unions are defined in the IDL using a :ref:`union_statement `. +A union shape MUST contain one or more named :ref:`members `. The following example defines a union shape with several members: .. tabs:: @@ -861,12 +864,68 @@ The following example defines a union shape with several members: } } -.. rubric:: Union member nullability +.. rubric:: Unit types in unions -Exactly one member of a union MUST be set to a non-null value. In protocol -serialization formats that support ``null`` values (for example, JSON), if a -``null`` value is provided for a union member, it is discarded as if it was -not provided. +Some union members might not need any meaningful information beyond the +tag itself. For these cases, union members MAY target Smithy's built-in +:ref:`unit type `, ``smithy.api#Unit``. + +The following example defines a union for actions a player can take in a +game. + +.. code-block:: smithy + + union PlayerAction { + /// Quit the game. + quit: Unit, + + /// Move in a specific direction. + move: DirectedAction, + + /// Jump in a specific direction. + jump: DirectedAction + } + + structure DirectedAction { + @required + direction: Integer + } + +The ``quit`` action has no meaningful data associated with it, while ``move`` +and ``jump`` both reference ``DirectedAction``. + +.. rubric:: Union member presence + +Exactly one member of a union MUST be set. The serialization of a union is +defined by a :ref:`protocol `, but for example +purposes, if unions were to be represented in a hypothetical JSON +serialization, the following value would be valid for the ``PlayerAction`` +union because a single member is present: + +.. code-block:: json + + { + "move": { + "direction": 1 + } + } + +The following value is **invalid** because multiple members are present: + +.. code-block:: json + + { + "quit": {}, + "move": { + "direction": 1 + } + } + +The following value is **invalid** because no members are present: + +.. code-block:: json + + {} .. rubric:: Adding new members @@ -882,6 +941,25 @@ by ``$``, followed by the member name. For example, the shape ID of the ``i32`` member in the above example is ``smithy.example#MyUnion$i32``. +.. _unit-type: + +Unit type +========= + +Smithy provides a singular `unit type`_ named ``smithy.api#Unit``. The unit +type in Smithy is similar to ``Void`` and ``None`` in other languages. It is +used when the input or output of an :ref:`operation ` has no +meaningful value or if a :ref:`union ` member has no meaningful value. +``smithy.api#Unit`` MUST NOT be referenced in any other context. + +The ``smithy.api#Unit`` shape is defined in Smithy's :ref:`prelude ` +as a structure shape marked with the ``smithy.api#unitType`` trait to +differentiate it from other structures. It is the only such structure in the +model that can be marked with the ``smithy.api#unitType`` trait. + +.. seealso:: :ref:`prelude`, :ref:`union`, :ref:`operation` + + Recursive shape definitions =========================== @@ -1253,70 +1331,33 @@ The following example defines a service that contains two shapes named "Widget" in its closure. The ``rename`` property is used to disambiguate the conflicting shapes. -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - namespace smithy.example + namespace smithy.example - service MyService { - version: "2017-02-11", - operations: [GetSomething], - rename: { - "foo.example#Widget": "FooWidget" - } + service MyService { + version: "2017-02-11", + operations: [GetSomething], + rename: { + "foo.example#Widget": "FooWidget" } + } - operation GetSomething { - output: GetSomethingOutput, - } + operation GetSomething { + input: GetSomethingInput, + output: GetSomethingOutput + } - structure GetSomethingOutput { - widget1: Widget, - fooWidget: foo.example#Widget, - } + @input + structure GetSomethingInput {} - structure Widget {} + @output + structure GetSomethingOutput { + widget1: Widget, + fooWidget: foo.example#Widget, + } - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyService": { - "type": "service", - "version": "2017-02-11", - "operations": [ - { - "target": "smithy.example#GetSomething" - } - ], - "rename": { - "foo.example#Widget": "FooWidget" - } - }, - "smithy.example#GetSomething": { - "type": "operation", - "output": { - "target": "smithy.example#GetSomethingOutput" - } - }, - "smithy.example#GetSomethingOutput": { - "type": "structure", - "members": { - "widget1": { - "target": "smithy.example#Widget" - }, - "fooWidget": { - "target": "foo.example#Widget" - } - } - }, - "smithy.example#Widget": { - "type": "structure" - } - } - } + structure Widget {} .. rubric:: Resources and operations can be bound once @@ -1347,63 +1388,77 @@ An operation supports the following members: - Description * - input - ``string`` - - The optional input ``structure`` of the operation. The value MUST be - a valid :ref:`shape ID ` that targets a - :ref:`structure ` shape. The targeted shape MUST NOT be - marked with the :ref:`error-trait`. + - The input of the operation defined using a :ref:`shape ID ` + that MUST target a structure. + + - Every operation SHOULD define a dedicated input shape marked with + the :ref:`input-trait`. Creating a dedicated input shape ensures + that input members can be added in the future if needed. + - Input defaults to :ref:`smithy.api#Unit ` if no input is + defined, indicating that the operation has no meaningful input. * - output - ``string`` - - The optional output ``structure`` of the operation. The value MUST - be a valid :ref:`shape ID ` that targets a - :ref:`structure ` shape. The targeted shape MUST NOT - be marked with the :ref:`error-trait`. + - The output of the operation defined using a :ref:`shape ID ` + that MUST target a structure. + + * Every operation SHOULD define a dedicated output shape marked with + the :ref:`output-trait`. Creating a dedicated output shape ensures + that output members can be added in the future if needed. + * Output defaults to :ref:`smithy.api#Unit ` if no output + is defined, indicating that the operation has no meaningful output. * - errors - [``string``] - - Defines the error ``structure``\s that an operation can return using - a set of shape IDs that MUST target :ref:`structure ` - shapes that are marked with the :ref:`error-trait`. + - The errors that an operation can return. Each string in the list is + a shape ID that MUST target a :ref:`structure ` shape + marked with the :ref:`error-trait`. -The following example defines an operation shape that accepts an input -structure named ``Input``, returns an output structure named ``Output``, and -can potentially return the ``NotFound`` or ``BadRequest`` -:ref:`error structures `. +The following example defines an operation that accepts an input structure +named ``MyOperationInput``, returns an output structure named +``MyOperationOutput``, and can potentially return the ``NotFound`` or +``BadRequest`` :ref:`error structures `. -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy + namespace smithy.example - namespace smithy.example + operation MyOperation { + input: MyOperationInput, + output: MyOperationOutput, + errors: [NotFound, BadRequest] + } - operation MyOperation { - input: Input, - output: Output, - errors: [NotFound, BadRequest] - } + @input + structure MyOperationInput {} - .. code-tab:: json + @output + structure MyOperationOutput {} - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyOperation": { - "type": "operation", - "input": { - "target": "smithy.example#Input" - }, - "output": { - "target": "smithy.example#Output" - }, - "errors": [ - { - "target": "smithy.example#NotFound" - }, - { - "target": "smithy.example#BadRequest" - } - ] - } - } - } +While, input and output SHOULD be explicitly defined for every operation, +omitting them is allowed. The default value for input and output is +``smithy.api#Unit``, indicating that there is no meaningful value. + +.. code-block:: smithy + + namespace smithy.example + + operation MySideEffectOperation {} + +The following example is equivalent, but more explicit in intent: + +.. code-block:: smithy + + namespace smithy.example + + operation MySideEffectOperation { + input: Unit, + output: Unit + } + +.. warning:: + + Using the ``Unit`` shape for input or output removes flexibility in how an + operation can evolve over time because members cannot be added to the + input or output if ever needed. .. _resource: @@ -1742,85 +1797,32 @@ contains member names that target the same shapes that are defined in the For example, given the following model, -.. tabs:: - - .. code-tab:: smithy - - resource Forecast { - identifiers: { - forecastId: ForecastId, - }, - read: GetForecast, - } - - @readonly - operation GetForecast { - input: GetForecastInput, - output: GetForecastOutput - } +.. code-block:: smithy - structure GetForecastInput { - @required + resource Forecast { + identifiers: { forecastId: ForecastId, - } + }, + read: GetForecast, + } - structure GetForecastOutput { - @required - weather: WeatherData, - } + @readonly + operation GetForecast { + input: GetForecastInput, + output: GetForecastOutput + } - .. code-tab:: json + @input + structure GetForecastInput { + @required + forecastId: ForecastId, + } - { - "smithy": "1.0", - "shapes": { - "smithy.example#Forecast": { - "type": "resource", - "identifiers": { - "forecastId": { - "target": "smithy.example#ForecastId" - } - }, - "read": { - "target": "smithy.example#GetForecast" - } - }, - "smithy.example#GetForecast": { - "type": "operation", - "input": { - "target": "smithy.example#GetForecastInput" - }, - "output": { - "target": "smithy.example#GetForecastOutput" - }, - "traits": { - "smithy.api#readonly": {} - } - }, - "smithy.example#GetForecastInput": { - "type": "structure", - "members": { - "forecastId": { - "target": "smithy.example#ForecastId", - "traits": { - "smithy.api#required": {} - } - } - } - }, - "smithy.example#GetForecastOutput": { - "type": "structure", - "members": { - "weather": { - "target": "smithy.example#WeatherData", - "traits": { - "smithy.api#required": {} - } - } - } - } - } - } + @output + structure GetForecastOutput { + @required + weather: WeatherData, + } ``GetForecast`` forms a valid instance operation because the operation is not marked with the ``collection`` trait and ``GetForecastInput`` provides @@ -1833,67 +1835,25 @@ for *all* child identifiers of the resource. Given the following model, -.. tabs:: - - .. code-tab:: smithy - - resource Forecast { - identifiers: { - forecastId: ForecastId, - }, - collectionOperations: [BatchPutForecasts], - } - - operation BatchPutForecasts { - input: BatchPutForecastsInput, - output: BatchPutForecastsOutput - } +.. code-block:: smithy - structure BatchPutForecastsInput { - @required - forecasts: BatchPutForecastList, - } + resource Forecast { + identifiers: { + forecastId: ForecastId, + }, + collectionOperations: [BatchPutForecasts], + } - .. code-tab:: json + operation BatchPutForecasts { + input: BatchPutForecastsInput, + output: BatchPutForecastsOutput + } - { - "smithy": "1.0", - "shapes": { - "smithy.example#Forecast": { - "type": "resource", - "identifiers": { - "forecastId": { - "target": "smithy.example#ForecastId" - } - }, - "collectionOperations": [ - { - "target": "smithy.example#BatchPutForecasts" - } - ] - }, - "smithy.example#BatchPutForecasts": { - "type": "operation", - "input": { - "target": "smithy.example#BatchPutForecastsInput" - }, - "output": { - "target": "smithy.example#BatchPutForecastsOutput" - } - }, - "smithy.example#BatchPutForecastsInput": { - "type": "structure", - "members": { - "forecasts": { - "target": "smithy.example#BatchPutForecastList", - "traits": { - "smithy.api#required": {} - } - } - } - } - } - } + @input + structure BatchPutForecastsInput { + @required + forecasts: BatchPutForecastList, + } ``BatchPutForecasts`` forms a valid collection operation with implicit identifier bindings because ``BatchPutForecastsInput`` does not require an @@ -1933,6 +1893,7 @@ For example, given the following, output: GetHistoricalForecastOutput } + @input structure GetHistoricalForecastInput { @required @resourceIdentifier("forecastId") @@ -2003,6 +1964,7 @@ The following example defines the ``PutForecast`` operation. output: PutForecastOutput } + @input structure PutForecastInput { // The client provides the resource identifier. @required @@ -2050,6 +2012,7 @@ The following example defines the ``CreateForecast`` operation. output: CreateForecastOutput } + @input structure CreateForecastInput { // No identifier is provided by the client, so the service is // responsible for providing the identifier of the resource. @@ -2079,6 +2042,7 @@ For example: errors: [ResourceNotFound] } + @input structure GetForecastInput { @required forecastId: ForecastId, @@ -2106,6 +2070,7 @@ For example: errors: [ResourceNotFound] } + @input structure UpdateForecastInput { @required forecastId: ForecastId, @@ -2136,6 +2101,7 @@ For example: errors: [ResourceNotFound] } + @input structure DeleteForecastInput { @required forecastId: ForecastId, @@ -2166,11 +2132,13 @@ For example: output: ListForecastsOutput } + @input structure ListForecastsInput { maxResults: Integer, nextToken: String } + @output structure ListForecastsOutput { nextToken: String, @required @@ -2930,3 +2898,4 @@ Implementations MUST take the following steps when merging two or more .. _tagged union data structure: https://en.wikipedia.org/wiki/Tagged_union .. _ubiquitous language: https://martinfowler.com/bliki/UbiquitousLanguage.html .. _context map: https://martinfowler.com/bliki/BoundedContext.html +.. _unit type: https://en.wikipedia.org/wiki/Unit_type diff --git a/docs/source/1.0/spec/core/prelude-model.rst b/docs/source/1.0/spec/core/prelude-model.rst index fa3cac1942a..a7db1af4dfd 100644 --- a/docs/source/1.0/spec/core/prelude-model.rst +++ b/docs/source/1.0/spec/core/prelude-model.rst @@ -73,3 +73,8 @@ Prelude shapes double Double double PrimitiveDouble + + /// The single unit type shape, similar to Void and None in other + /// languages, used to represent no meaningful value. + @unitType + structure Unit {} diff --git a/docs/source/1.0/spec/core/resource-traits.rst b/docs/source/1.0/spec/core/resource-traits.rst index dc2fa8b9efd..a88cd6e05f4 100644 --- a/docs/source/1.0/spec/core/resource-traits.rst +++ b/docs/source/1.0/spec/core/resource-traits.rst @@ -289,35 +289,34 @@ provided must correspond to the name of an identifier for said resource. The trait is not required when the name of the input structure member is an exact match for the name of the resource identifier. -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - resource File { - identifiers: { - directory: "String", - fileName: "String", - }, - read: GetFile, - } + resource File { + identifiers: { + directory: "String", + fileName: "String", + }, + read: GetFile, + } - @readonly - operation GetFile { - input: GetFileInput, - output: GetFileOutput, - errors: [NoSuchResource] - } + @readonly + operation GetFile { + input: GetFileInput, + output: GetFileOutput, + errors: [NoSuchResource] + } - structure GetFileInput { - @required - directory: String, + @input + structure GetFileInput { + @required + directory: String, - // resourceIdentifier is used because the input member name - // does not match the resource identifier name - @resourceIdentifier("fileName") - @required - name: String, - } + // resourceIdentifier is used because the input member name + // does not match the resource identifier name + @resourceIdentifier("fileName") + @required + name: String, + } .. _CreateTable: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html diff --git a/docs/source/1.0/spec/core/selectors.rst b/docs/source/1.0/spec/core/selectors.rst index da1cc734ea7..5841e0a4920 100644 --- a/docs/source/1.0/spec/core/selectors.rst +++ b/docs/source/1.0/spec/core/selectors.rst @@ -753,6 +753,7 @@ in the closure of a service. @tags(["invalid"]) operation OperationD {} + @input structure OperationAInput { badValue: BadEnum, goodValue: GoodEnum, diff --git a/docs/source/1.0/spec/core/stream-traits.rst b/docs/source/1.0/spec/core/stream-traits.rst index ee3f92f528d..fa2421fc725 100644 --- a/docs/source/1.0/spec/core/stream-traits.rst +++ b/docs/source/1.0/spec/core/stream-traits.rst @@ -42,22 +42,25 @@ Validation only a single member of a structure can target a shape marked as ``streaming``. -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy + operation StreamingOperation { + input: StreamingOperationInput, + output: StreamingOperationOutput, + } - operation StreamingOperation { - output: StreamingOperationOutput, - } + @input + structure StreamingOperationInput {} - structure StreamingOperationOutput { - @required - streamId: String - output: StreamingBlob, - } + @output + structure StreamingOperationOutput { + @required + streamId: String + output: StreamingBlob, + } - @streaming - blob StreamingBlob + @streaming + blob StreamingBlob .. smithy-trait:: smithy.api#requiresLength @@ -115,181 +118,73 @@ that is sent over the event stream. The following example defines an operation that uses an event stream in its input by referencing a member that targets a union: -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example +.. code-block:: smithy - operation PublishMessages { - input: PublishMessagesInput - } + namespace smithy.example - structure PublishMessagesInput { - room: String, - messages: PublishEvents, - } + operation PublishMessages { + input: PublishMessagesInput + } - @streaming - union PublishEvents { - message: Message, - leave: LeaveEvent, - } + @input + structure PublishMessagesInput { + room: String, + messages: PublishEvents, + } - structure Message { - message: String, - } + @streaming + union PublishEvents { + message: Message, + leave: LeaveEvent, + } - structure LeaveEvent {} + structure Message { + message: String, + } - .. code-tab:: json + structure LeaveEvent {} - { - "smithy": "1.0", - "shapes": { - "smithy.example#PublishMessages": { - "type": "operation", - "input": { - "target": "smithy.example#PublishMessagesInput" - } - }, - "smithy.example#PublishMessagesInput": { - "type": "structure", - "members": { - "room": { - "target": "smithy.api#String" - }, - "messages": { - "target": "smithy.example#PublishEvents" - } - } - }, - "smithy.example#PublishEvents": { - "type": "union", - "members": { - "message": { - "target": "smithy.example#Message" - }, - "leave": { - "target": "smithy.example#LeaveEvent" - } - }, - "traits": { - "smithy.api#streaming": {} - } - }, - "smithy.example#Message": { - "type": "structure", - "members": { - "message": { - "target": "smithy.api#String" - } - } - } - } - } .. _output-eventstream: The following example defines an operation that uses an event stream in its output: -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - operation SubscribeToMovements { - output: SubscribeToMovementsOutput - } +.. code-block:: smithy - structure SubscribeToMovementsOutput { - movements: MovementEvents, - } + namespace smithy.example - @streaming - union MovementEvents { - up: Movement, - down: Movement, - left: Movement, - right: Movement, - throttlingError: ThrottlingError - } + operation SubscribeToMovements { + input: SubscribeToMovementsInput, + output: SubscribeToMovementsOutput + } - structure Movement { - velocity: Float, - } + @input + structure SubscribeToMovementsInput {} - /// An example error emitted when the client is throttled - /// and should terminate the event stream. - @error("client") - @retryable(throttling: true) - structure ThrottlingError {} + @output + structure SubscribeToMovementsOutput { + movements: MovementEvents, + } + @streaming + union MovementEvents { + up: Movement, + down: Movement, + left: Movement, + right: Movement, + throttlingError: ThrottlingError + } - .. code-tab:: json + structure Movement { + velocity: Float, + } - { - "smithy": "1.0", - "shapes": { - "smithy.example#SubscribeToMovements": { - "type": "operation", - "output": { - "target": "smithy.example#SubscribeToMovementsOutput" - } - }, - "smithy.example#SubscribeToMovementsOutput": { - "type": "structure", - "members": { - "movements": { - "target": "smithy.example#MovementEvents" - } - } - }, - "smithy.example#MovementEvents": { - "type": "union", - "members": { - "up": { - "target": "smithy.example#Movement" - }, - "down": { - "target": "smithy.example#Movement" - }, - "left": { - "target": "smithy.example#Movement" - }, - "right": { - "target": "smithy.example#Movement" - }, - "throttlingError": { - "target": "smithy.example#ThrottlingError" - } - }, - "traits": { - "smithy.api#streaming": {} - } - }, - "smithy.example#Movement": { - "type": "structure", - "members": { - "velocity": { - "target": "smithy.api#Float" - } - } - }, - "smithy.example#ThrottlingError": { - "type": "structure", - "traits": { - "smithy.api#documentation": "An example error emitted when the client is throttled and should terminate the event stream.", - "smithy.api#error": "client", - "smithy.api#retryable": { - "throttling": true - } - } - } - } - } + /// An example error emitted when the client is throttled + /// and should terminate the event stream. + @error("client") + @retryable(throttling: true) + structure ThrottlingError {} Modeled errors in event streams @@ -331,91 +226,34 @@ The following example defines an operation with an input event stream with an initial-request. The client will first send the initial-request to the service, followed by the events sent in the payload of the HTTP message. -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example +.. code-block:: smithy - @http(method: "POST", uri: "/messages/{room}") - operation PublishMessages { - input: PublishMessagesInput - } + namespace smithy.example - structure PublishMessagesInput { - @httpLabel - @required - room: String, + @http(method: "POST", uri: "/messages/{room}") + operation PublishMessages { + input: PublishMessagesInput + } - @httpPayload - messages: MessageStream, - } + @input + structure PublishMessagesInput { + @httpLabel + @required + room: String, - @streaming - union MessageStream { - message: Message, - } + @httpPayload + messages: MessageStream, + } - structure Message { - message: String, - } + @streaming + union MessageStream { + message: Message, + } - .. code-tab:: json + structure Message { + message: String, + } - { - "smithy": "1.0", - "shapes": { - "smithy.example#PublishMessages": { - "type": "operation", - "input": { - "target": "smithy.example#PublishMessagesInput" - }, - "traits": { - "smithy.api#http": { - "uri": "/messages/{room}", - "method": "POST" - } - } - }, - "smithy.example#PublishMessagesInput": { - "type": "structure", - "members": { - "room": { - "target": "smithy.api#String", - "traits": { - "smithy.api#httpLabel:": {}, - "smithy.api#required": {} - } - }, - "messages": { - "target": "smithy.example#MessageStream", - "traits": { - "smithy.api#httpPayload": {} - } - } - } - }, - "smithy.example#MessageStream": { - "type": "union", - "members": { - "message": { - "target": "smithy.example#Message" - } - }, - "traits": { - "smithy.api#streaming": {} - } - }, - "smithy.example#Message": { - "type": "structure", - "members": { - "message": { - "target": "smithy.api#String" - } - } - } - } - } .. _initial-response: @@ -437,80 +275,32 @@ an initial-response. The client will first receive and process the initial-response, followed by the events sent in the payload of the HTTP message. -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - namespace smithy.example + namespace smithy.example - @http(method: "GET", uri: "/messages/{room}") - operation SubscribeToMessages { - input: SubscribeToMessagesInput, - output: SubscribeToMessagesOutput - } + @http(method: "GET", uri: "/messages/{room}") + operation SubscribeToMessages { + input: SubscribeToMessagesInput, + output: SubscribeToMessagesOutput + } - structure SubscribeToMessagesInput { - @httpLabel - @required - room: String - } + @input + structure SubscribeToMessagesInput { + @httpLabel + @required + room: String + } - structure SubscribeToMessagesOutput { - @httpHeader("X-Connection-Lifetime") - connectionLifetime: Integer, + @output + structure SubscribeToMessagesOutput { + @httpHeader("X-Connection-Lifetime") + connectionLifetime: Integer, - @httpPayload - messages: MessageStream, - } + @httpPayload + messages: MessageStream, + } - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#PublishMessages": { - "type": "operation", - "input": { - "target": "smithy.example#PublishMessagesInput" - }, - "traits": { - "smithy.api#http": { - "uri": "/messages/{room}", - "method": "POST" - } - } - }, - "smithy.example#SubscribeToMessagesInput": { - "type": "structure", - "members": { - "room": { - "target": "smithy.api#String", - "traits": { - "smithy.api#httpLabel:": {}, - "smithy.api#required": {} - } - } - } - }, - "smithy.example#SubscribeToMessagesOutput": { - "type": "structure", - "members": { - "connectionLifetime": { - "target": "smithy.api#Integer", - "traits": { - "smithy.api#httpHeader:": "X-Connection-Lifetime" - } - }, - "messages": { - "target": "smithy.example#MessageStream", - "traits": { - "smithy.api#httpPayload": {} - } - } - } - } - } - } Initial message client and server behavior ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -546,9 +336,14 @@ on the name of an event. For example, given the following event stream: namespace smithy.example operation SubscribeToEvents { + input: SubscribeToEventsInput, output: SubscribeToEventsOutput } + @input + structure SubscribeToEventsInput {} + + @output structure SubscribeToEventsOutput { events: Events, } diff --git a/docs/source/1.0/spec/core/type-refinement-traits.rst b/docs/source/1.0/spec/core/type-refinement-traits.rst index 6249a63721d..e0659e17146 100644 --- a/docs/source/1.0/spec/core/type-refinement-traits.rst +++ b/docs/source/1.0/spec/core/type-refinement-traits.rst @@ -138,6 +138,65 @@ in Java). } +.. smithy-trait:: smithy.api#input +.. _input-trait: + +--------------- +``input`` trait +--------------- + +Summary + Specializes a structure for use only as the input of a single operation. +Trait selector + ``structure`` +Value type + Annotation trait. +Conflicts with + :ref:`output-trait`, :ref:`error-trait` + +Structure shapes marked with the ``@input`` trait MUST adhere to the +following constraints: + +1. They can only be referenced in the model as an operation's input. +2. They cannot be used as the input of more than one operation. +3. They SHOULD have a shape name that starts with the name of the + operation that targets it (if any). For example, the input shape of the + ``GetSprocket`` operation SHOULD be named ``GetSprocketInput``, + ``GetSprocketRequest``, or something similar. + +These constraints allow tooling to specialize operation input shapes in +ways that would otherwise require complex model transformations. + + +.. smithy-trait:: smithy.api#output +.. _output-trait: + +---------------- +``output`` trait +---------------- + +Summary + Specializes a structure for use only as the output of a single operation. +Trait selector + ``structure`` +Value type + Annotation trait. +Conflicts with + :ref:`input-trait`, :ref:`error-trait` + +Structure shapes marked with the ``@output`` trait MUST adhere to the +following constraints: + +1. They can only be referenced in the model as an operation's output. +2. They cannot be used as the output of more than one operation. +3. They SHOULD have a shape name that starts with the name of the + operation that targets it (if any). For example, the output shape of the + ``GetSprocket`` operation SHOULD be named ``GetSprocketOutput``. + +These constraints allow tooling to specialize operation output shapes in +ways that would otherwise require complex model transformations. + + .. smithy-trait:: smithy.api#sparse .. _sparse-trait: diff --git a/docs/source/1.0/spec/http-protocol-compliance-tests.rst b/docs/source/1.0/spec/http-protocol-compliance-tests.rst index 37169752a5f..734ae95fbbd 100644 --- a/docs/source/1.0/spec/http-protocol-compliance-tests.rst +++ b/docs/source/1.0/spec/http-protocol-compliance-tests.rst @@ -257,126 +257,57 @@ HTTP request example The following example defines a protocol compliance test for a JSON protocol that uses :ref:`HTTP binding traits `. -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - namespace smithy.example + namespace smithy.example - use smithy.test#httpRequestTests + use smithy.test#httpRequestTests - @endpoint(hostPrefix: "{hostLabel}.prefix.") - @http(method: "POST", uri: "/") - @httpRequestTests([ - { - id: "say_hello", - protocol: exampleProtocol, - params: { - "hostLabel": "foo", - "greeting": "Hi", - "name": "Teddy", - "query": "Hello there" - }, - method: "POST", - host: "example.com", - resolvedHost: "foo.prefix.example.com", - uri: "/", - queryParams: [ - "Hi=Hello%20there" - ], - headers: { - "X-Greeting": "Hi", - }, - body: "{\"name\": \"Teddy\"}", - bodyMediaType: "application/json" - } - ]) - operation SayHello { - input: SayHelloInput + @endpoint(hostPrefix: "{hostLabel}.prefix.") + @http(method: "POST", uri: "/") + @httpRequestTests([ + { + id: "say_hello", + protocol: exampleProtocol, + params: { + "hostLabel": "foo", + "greeting": "Hi", + "name": "Teddy", + "query": "Hello there" + }, + method: "POST", + host: "example.com", + resolvedHost: "foo.prefix.example.com", + uri: "/", + queryParams: [ + "Hi=Hello%20there" + ], + headers: { + "X-Greeting": "Hi", + }, + body: "{\"name\": \"Teddy\"}", + bodyMediaType: "application/json" } + ]) + operation SayHello { + input: SayHelloInput, + output: Unit + } - structure SayHelloInput { - @required - @hostLabel - hostLabel: String, + @input + structure SayHelloInput { + @required + @hostLabel + hostLabel: String, - @httpHeader("X-Greeting") - greeting: String, + @httpHeader("X-Greeting") + greeting: String, - @httpQuery("Hi") - query: String, - - name: String - } + @httpQuery("Hi") + query: String, - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#SayHello": { - "type": "operation", - "input": { - "target": "smithy.example#SayHelloInput" - }, - "traits": { - "smithy.api#endpoint": { - "hostPrefix": "{hostLabel}.prefix." - }, - "smithy.api#http": { - "method": "POST", - "uri": "/", - "code": 200 - }, - "smithy.test#httpRequestTests": [ - { - "id": "say_hello", - "protocol": "smithy.example#exampleProtocol", - "method": "POST", - "host": "example.com", - "resolvedHost": "foo.prefix.example.com", - "uri": "/", - "headers": { - "X-Greeting": "Hi" - }, - "queryParams": [ - "Hi=Hello%20there" - ], - "body": "{\"name\": \"Teddy\"}", - "bodyMediaType": "application/json" - "params": { - "hostLabel": "foo", - "greeting": "Hi", - "name": "Teddy", - "query": "Hello there" - } - } - ] - } - }, - "smithy.example#SayHelloInput": { - "type": "structure", - "members": { - "hostLabel": { - "target": "smithy.api#String", - "traits": { - "smithy.api#required": {}, - "smithy.api#hostLabel": {} - } - }, - "greeting": { - "target": "smithy.api#String", - "traits": { - "smithy.api#httpHeader": "X-Greeting" - } - }, - "name": { - "target": "smithy.api#String" - } - } - } - } - } + name: String + } .. smithy-trait:: smithy.test#httpResponseTests @@ -511,81 +442,38 @@ HTTP response example The following example defines a protocol compliance test for a JSON protocol that uses :ref:`HTTP binding traits `. -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy + namespace smithy.example - namespace smithy.example - - use smithy.test#httpResponseTests - - @http(method: "POST", uri: "/") - @httpResponseTests([ - { - id: "say_goodbye", - protocol: exampleProtocol, - params: {farewell: "Bye"}, - code: 200, - headers: { - "X-Farewell": "Bye", - "Content-Length": "0" - } - } - ]) - operation SayGoodbye { - output: SayGoodbyeOutput - } - - structure SayGoodbyeOutput { - @httpHeader("X-Farewell") - farewell: String, - } - - .. code-tab:: json + use smithy.test#httpResponseTests + @http(method: "POST", uri: "/") + @httpResponseTests([ { - "smithy": "1.0", - "shapes": { - "smithy.example#SayGoodbye": { - "type": "operation", - "output": { - "target": "smithy.example#SayGoodbyeOutput" - }, - "traits": { - "smithy.api#http": { - "method": "POST", - "uri": "/", - "code": 200 - }, - "smithy.test#httpResponseTests": [ - { - "id": "say_goodbye", - "protocol": "smithy.example#exampleProtocol", - "headers": { - "Content-Length": "0", - "X-Farewell": "Bye" - }, - "params": { - "farewell": "Bye" - }, - "code": 200 - } - ] - } - }, - "smithy.example#SayGoodbyeOutput": { - "type": "structure", - "members": { - "farewell": { - "target": "smithy.api#String", - "traits": { - "smithy.api#httpHeader": "X-Farewell" - } - } - } - } + id: "say_goodbye", + protocol: exampleProtocol, + params: {farewell: "Bye"}, + code: 200, + headers: { + "X-Farewell": "Bye", + "Content-Length": "0" } } + ]) + operation SayGoodbye { + input: SayGoodbyeInput, + output: SayGoodbyeOutput + } + + @input + structure SayGoodbyeInput {} + + @output + structure SayGoodbyeOutput { + @httpHeader("X-Farewell") + farewell: String, + } HTTP error response example diff --git a/docs/source/1.0/spec/mqtt.rst b/docs/source/1.0/spec/mqtt.rst index c15c1936257..1be6f6dd07b 100644 --- a/docs/source/1.0/spec/mqtt.rst +++ b/docs/source/1.0/spec/mqtt.rst @@ -87,68 +87,29 @@ MQTT topic. The following example defines a publish operation with two labels, ``{first}`` and ``{second}``, in the MQTT topic template: -.. tabs:: - - .. code-tab:: smithy - - use smithy.mqtt#publish - use smithy.mqtt#topicLabel - - @publish("{first}/{second}") - operation ExampleOperation { - input: ExampleOperationInput - } - - structure ExampleOperationInput { - @required - @topicLabel - first: String, - - @required - @topicLabel - second: String, - - message: String, - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#ExampleOperation": { - "type": "operation", - "input": { - "target": "smithy.example#ExampleOperationInput" - }, - "traits": { - "smithy.mqtt#publish": "{first}/{second}" - } - }, - "smithy.example#ExampleOperationInput": { - "type": "structure", - "members": { - "first": { - "target": "smithy.api#String", - "traits": { - "smithy.api#required": {}, - "smithy.mqtt#topicLabel": {} - } - }, - "second": { - "target": "smithy.api#String", - "traits": { - "smithy.api#required": {}, - "smithy.mqtt#topicLabel": {} - } - }, - "message": { - "target": "smithy.api#String" - } - } - } - } - } +.. code-block:: smithy + + use smithy.mqtt#publish + use smithy.mqtt#topicLabel + + @publish("{first}/{second}") + operation ExampleOperation { + input: ExampleOperationInput, + output: Unit + } + + @input + structure ExampleOperationInput { + @required + @topicLabel + first: String, + + @required + @topicLabel + second: String, + + message: String, + } MQTT topic templates MUST adhere to the following constraints: @@ -181,9 +142,9 @@ Trait summary Trait selector .. code-block:: none - operation:not(-[output]->) + operation:not(-[output]-> * > member) - *An operation that does not define output* + *An operation whose output has no members* Trait value ``string`` value that is a valid :ref:`MQTT topic template `. The provided topic @@ -200,63 +161,28 @@ come together to form the protocol-specific payload of the PUBLISH message. The following example defines an operation that publishes messages to the ``foo/{bar}`` topic: -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - use smithy.mqtt#publish - use smithy.mqtt#topicLabel - - @publish("foo/{bar}") - operation PostFoo { - input: PostFooInput - } - - structure PostFooInput { - @required - @topicLabel - bar: String, - - someValue: String, - anotherValue: Boolean, - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#PostFoo": { - "type": "operation", - "input": { - "target": "smithy.example#PostFooInput" - }, - "traits": { - "smithy.mqtt#publish": "foo/{bar}" - } - }, - "smithy.example#PostFooInput": { - "type": "structure", - "members": { - "bar": { - "target": "smithy.api#String", - "traits": { - "smithy.api#required": {}, - "smithy.mqtt#topicLabel": {} - } - }, - "message": { - "target": "smithy.api#String" - }, - "anotherValue": { - "target": "smithy.api#Boolean" - } - } - } - } - } +.. code-block:: smithy + + namespace smithy.example + + use smithy.mqtt#publish + use smithy.mqtt#topicLabel + + @publish("foo/{bar}") + operation PostFoo { + input: PostFooInput, + output: Unit + } + + @input + structure PostFooInput { + @required + @topicLabel + bar: String, + + someValue: String, + anotherValue: Boolean, + } The "bar" member of the above ``PostFoo`` operation is marked with the :ref:`smithy.mqtt#topicLabel-trait`, indicating that the member provides a @@ -268,7 +194,7 @@ that is sent in the payload of the message. Publish validation ================== -* Publish operations MUST NOT define output. +* Publish operations MUST NOT have output with members. * Publish operations MUST NOT utilize input event streams. * Publish operations SHOULD NOT define errors. * Publish MQTT topics MUST NOT conflict with other publish MQTT topics or @@ -319,93 +245,38 @@ from topics. The following example operation subscribes to the ``events/{id}`` topic using an :ref:`event stream `: -.. tabs:: - - .. code-tab:: smithy - - use smithy.mqtt#subscribe - use smithy.mqtt#topicLabel - - @subscribe("events/{id}") - operation SubscribeForEvents { - input: SubscribeForEventsInput, - output: SubscribeForEventsOutput - } - - structure SubscribeForEventsInput { - @required - @topicLabel - id: String, - } - - structure SubscribeForEventsOutput { - events: EventStream, - } - - @streaming - union EventStream { - message: Event, - } - - structure Event { - message: String, - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#SubscribeForEvents": { - "type": "operation", - "input": { - "target": "smithy.example#SubscribeForEventsInput" - }, - "traits": { - "smithy.mqtt#subscribe": "events/{id}" - } - }, - "smithy.example#SubscribeForEventsInput": { - "type": "structure", - "members": { - "id": { - "target": "smithy.api#String", - "traits": { - "smithy.api#required": {}, - "smithy.mqtt#topicLabel": {} - } - } - } - }, - "smithy.example#SubscribeForEventsOutput": { - "type": "structure", - "members": { - "events": { - "target": "smithy.example#EventStream" - } - } - }, - "smithy.example#EventStream": { - "type": "union", - "members": { - "message": { - "target": "smithy.example#Event" - } - }, - "traits" { - "smithy.api#streaming": {} - } - }, - "smithy.example#Event": { - "type": "structure", - "members": { - "message": { - "target": "smithy.api#String" - } - } - } - } - } +.. code-block:: smithy + + use smithy.mqtt#subscribe + use smithy.mqtt#topicLabel + + @subscribe("events/{id}") + operation SubscribeForEvents { + input: SubscribeForEventsInput, + output: SubscribeForEventsOutput + } + + @input + structure SubscribeForEventsInput { + @required + @topicLabel + id: String, + } + + @output + structure SubscribeForEventsOutput { + events: EventStream, + } + + @streaming + union EventStream { + message: Event, + } + + structure Event { + message: String, + } + Subscribe validation ==================== @@ -534,7 +405,7 @@ MQTT protocol bindings. @protocolDefinition structure mqttJson {} - @trait(selector: "operation:not(-[output]->)", + @trait(selector: "operation:not(-[output]-> * > member)", conflicts: ["smithy.mqtt#subscribe"]) @tags(["diff.error.const"]) // Matches one or more characters that are not "#" or "+". diff --git a/docs/source/1.0/spec/waiters.rst b/docs/source/1.0/spec/waiters.rst index 1856c6c30f8..0550a5b9440 100644 --- a/docs/source/1.0/spec/waiters.rst +++ b/docs/source/1.0/spec/waiters.rst @@ -413,9 +413,8 @@ members MUST be set: - Matches on both the input and output of an operation using a JMESPath_ expression. Input parameters are available through the top-level ``input`` field, and output data is available through the top-level - ``output`` field. This matcher MUST NOT be used on operations that - do not define input or output. This matcher is checked only if an - operation completes successfully. + ``output`` field. This matcher is checked only if an operation + completes successfully. * - success - ``boolean`` - When set to ``true``, matches when an operation returns a successful @@ -635,11 +634,13 @@ triggered if the ``status`` property equals ``failed``. output: GetThingOutput, } + @input structure GetThingInput { @required name: String, } + @output structure GetThingOutput { status: String } diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index dfee53d22c7..e5595b81c23 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -105,29 +105,15 @@ weather service. ``Weather`` is a :ref:`service` shape that is defined inside of a :ref:`namespace `. -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy + namespace example.weather - namespace example.weather - - /// Provides weather forecasts. - /// Triple slash comments attach documentation to shapes. - service Weather { - version: "2006-03-01" - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "example.weather#Weather": { - "type": "service", - "version": "2006-03-01" - } - } - } + /// Provides weather forecasts. + /// Triple slash comments attach documentation to shapes. + service Weather { + version: "2006-03-01" + } .. admonition:: What's that syntax? :class: note @@ -150,64 +136,25 @@ Defining resources A resource is contained within a service or another resource. Resources have identifiers, operations, and any number of child resources. -.. tabs:: +.. code-block:: smithy - .. code-tab:: smithy + namespace example.weather - namespace example.weather - - /// Provides weather forecasts. - service Weather { - version: "2006-03-01", - resources: [City] - } - - resource City { - identifiers: { cityId: CityId }, - read: GetCity, - list: ListCities, - } + /// Provides weather forecasts. + service Weather { + version: "2006-03-01", + resources: [City] + } - // "pattern" is a trait. - @pattern("^[A-Za-z0-9 ]+$") - string CityId + resource City { + identifiers: { cityId: CityId }, + read: GetCity, + list: ListCities, + } - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "example.weather#Weather": { - "type": "service", - "version": "2006-03-01", - "resources": [ - { - "target": "example.weather#City" - } - ] - }, - "example.weather#City": { - "type": "resource", - "identifiers": { - "cityId": { - "target": "example.weather#CityId" - } - }, - "read": { - "target": "example.weather#GetCity" - }, - "list": { - "target": "example.weather#ListCities" - } - }, - "example.weather#CityId": { - "type": "string", - "traits": { - "smithy.api#pattern": "^[A-Za-z0-9 ]+$" - } - } - } - } + // "pattern" is a trait. + @pattern("^[A-Za-z0-9 ]+$") + string CityId Because the ``Weather`` service contains many cities, the ``City`` resource defines an :ref:`identifier `. *Identifiers* are used @@ -222,59 +169,19 @@ identity of the resource. Each ``City`` has a single ``Forecast``. This can be defined by adding the ``Forecast`` to the ``resources`` property of the ``City``. -.. tabs:: - - .. code-tab:: smithy - - resource City { - identifiers: { cityId: CityId }, - read: GetCity, - list: ListCities, - resources: [Forecast], - } +.. code-block:: smithy - resource Forecast { - identifiers: { cityId: CityId }, - read: GetForecast, - } - - .. code-tab:: json + resource City { + identifiers: { cityId: CityId }, + read: GetCity, + list: ListCities, + resources: [Forecast], + } - { - "smithy": "1.0", - "shapes": { - "example.weather#City": { - "type": "resource", - "identifiers": { - "cityId": { - "target": "example.weather#CityId" - } - }, - "read": { - "target": "example.weather#GetCity" - }, - "list": { - "target": "example.weather#ListCities" - }, - "resources": [ - { - "target": "example.weather#Forecast" - } - ] - }, - "example.weather#Forecast": { - "type": "resource", - "identifiers": { - "cityId": { - "target": "example.weather#CityId" - } - }, - "read": { - "target": "example.weather#GetForecast" - } - } - } - } + resource Forecast { + identifiers: { cityId: CityId }, + read: GetForecast, + } Child resources must define the exact same identifiers property of their parent, but they are allowed to add any number of additional identifiers if @@ -304,192 +211,72 @@ your API. Let's define the operation used to "read" a ``City``. -.. tabs:: - - .. code-tab:: smithy - - @readonly - operation GetCity { - input: GetCityInput, - output: GetCityOutput, - errors: [NoSuchResource] - } - - structure GetCityInput { - // "cityId" provides the identifier for the resource and - // has to be marked as required. - @required - cityId: CityId - } +.. code-block:: smithy - structure GetCityOutput { - // "required" is used on output to indicate if the service - // will always provide a value for the member. - @required - name: String, + @readonly + operation GetCity { + input: GetCityInput, + output: GetCityOutput, + errors: [NoSuchResource] + } - @required - coordinates: CityCoordinates, - } + @input + structure GetCityInput { + // "cityId" provides the identifier for the resource and + // has to be marked as required. + @required + cityId: CityId + } - structure CityCoordinates { - @required - latitude: Float, + @output + structure GetCityOutput { + // "required" is used on output to indicate if the service + // will always provide a value for the member. + @required + name: String, - @required - longitude: Float, - } + @required + coordinates: CityCoordinates, + } - // "error" is a trait that is used to specialize - // a structure as an error. - @error("client") - structure NoSuchResource { - @required - resourceType: String - } + structure CityCoordinates { + @required + latitude: Float, - .. code-tab:: json + @required + longitude: Float, + } - { - "smithy": "1.0", - "shapes": { - "example.weather#GetCity": { - "type": "operation", - "input": { - "target": "example.weather#GetCityInput" - }, - "output": { - "target": "example.weather#GetCityOutput" - }, - "errors": [ - { - "target": "example.weather#NoSuchResource" - } - ] - }, - "example.weather#GetCityInput": { - "type": "structure", - "members": { - "cityId": { - "target": "example.weather#CityId", - "traits": { - "smithy.api#required": true - } - } - } - }, - "example.weather#GetCityOutput": { - "type": "structure", - "members": { - "name": { - "target": "smithy.api#String", - "traits": { - "smithy.api#required": true - } - }, - "coordinates": { - "target": "example.weather#CityCoordinates", - "traits": { - "smithy.api#required": true - } - } - } - }, - "example.weather#CityCoordinates": { - "type": "structure", - "members": { - "latitude": { - "target": "smithy.api#Float", - "traits": { - "smithy.api#required": true - } - }, - "longitude": { - "target": "smithy.api#Float", - "traits": { - "smithy.api#required": true - } - } - } - }, - "example.weather#NoSuchResource": { - "type": "structure", - "members": { - "resourceType": { - "target": "smithy.api#String", - "traits": { - "smithy.api#required": true - } - } - }, - "traits": { - "smithy.api#error": "client" - } - } - } - } + // "error" is a trait that is used to specialize + // a structure as an error. + @error("client") + structure NoSuchResource { + @required + resourceType: String + } And define the operation used to "read" a ``Forecast``. -.. tabs:: - - .. code-tab:: smithy - - @readonly - operation GetForecast { - input: GetForecastInput, - output: GetForecastOutput - } - - // "cityId" provides the only identifier for the resource since - // a Forecast doesn't have its own. - structure GetForecastInput { - @required - cityId: CityId, - } - - structure GetForecastOutput { - chanceOfRain: Float - } +.. code-block:: smithy - .. code-tab:: json + @readonly + operation GetForecast { + input: GetForecastInput, + output: GetForecastOutput + } - { - "smithy": "1.0", - "shapes": { - "example.weather#GetForecast": { - "type": "operation", - "input": { - "target": "example.weather#GetForecastInput" - }, - "output": { - "target": "example.weather#GetForecastOutput" - }, - "traits": { - "smithy.api#readonly": true - } - }, - "example.weather#GetForecastInput": { - "type": "structure", - "members": { - "cityId": { - "target": "example.weather#CityId", - "traits": { - "smithy.api#required": true - } - } - } - }, - "example.weather#GetForecastOutput": { - "type": "structure", - "members": { - "chanceOfRain": { - "target": "smithy.api#Float" - } - } - } - } - } + // "cityId" provides the only identifier for the resource since + // a Forecast doesn't have its own. + @input + structure GetForecastInput { + @required + cityId: CityId, + } + + @output + structure GetForecastOutput { + chanceOfRain: Float + } .. admonition:: Review :class: tip @@ -511,141 +298,54 @@ is a :ref:`collection operation `, and as such, MUST NOT bind the identifier of a ``City`` to its input structure; we are listing cities, so there's no way we could provide a ``City`` identifier. -.. tabs:: - - .. code-tab:: smithy - - /// Provides weather forecasts. - @paginated(inputToken: "nextToken", outputToken: "nextToken", - pageSize: "pageSize") - service Weather { - version: "2006-03-01", - resources: [City] - } - - // The paginated trait indicates that the operation may - // return truncated results. Applying this trait to the service - // sets default pagination configuration settings on each operation. - @paginated(items: "items") - @readonly - operation ListCities { - input: ListCitiesInput, - output: ListCitiesOutput - } +.. code-block:: smithy - structure ListCitiesInput { - nextToken: String, - pageSize: Integer - } + /// Provides weather forecasts. + @paginated(inputToken: "nextToken", outputToken: "nextToken", + pageSize: "pageSize") + service Weather { + version: "2006-03-01", + resources: [City] + } - structure ListCitiesOutput { - nextToken: String, + // The paginated trait indicates that the operation may + // return truncated results. Applying this trait to the service + // sets default pagination configuration settings on each operation. + @paginated(items: "items") + @readonly + operation ListCities { + input: ListCitiesInput, + output: ListCitiesOutput + } - @required - items: CitySummaries, - } + @input + structure ListCitiesInput { + nextToken: String, + pageSize: Integer + } - // CitySummaries is a list of CitySummary structures. - list CitySummaries { - member: CitySummary - } + @output + structure ListCitiesOutput { + nextToken: String, - // CitySummary contains a reference to a City. - @references([{resource: City}]) - structure CitySummary { - @required - cityId: CityId, + @required + items: CitySummaries, + } - @required - name: String, - } + // CitySummaries is a list of CitySummary structures. + list CitySummaries { + member: CitySummary + } - .. code-tab:: json + // CitySummary contains a reference to a City. + @references([{resource: City}]) + structure CitySummary { + @required + cityId: CityId, - { - "smithy": "1.0", - "shapes": { - "example.weather#Weather": { - "type": "service", - "version": "2006-03-01", - "resources": [ - { - "target": "example.weather#City" - } - ], - "traits": { - "smithy.api#paginated": { - "inputToken": "nextToken", - "outputToken": "nextToken", - "pageSize": "pageSize" - } - } - }, - "example.weather#ListCities": { - "type": "operation", - "input": { - "target": "example.weather#ListCitiesInput" - }, - "output": { - "target": "example.weather#ListCitiesOutput" - }, - "traits": { - "smithy.api#readonly": true, - "smithy.api#paginated": { - "items": "items" - } - } - }, - "example.weather#ListCitiesInput": { - "type": "structure", - "members": { - "nextToken": { - "target": "smithy.api#String" - }, - "pageSize": { - "target": "smithy.api#Integer" - } - } - }, - "example.weather#ListCitiesOutput": { - "type": "structure", - "members": { - "nextToken": { - "target": "smithy.api#String" - }, - "items": { - "target": "example.weather#CitySummaries", - "traits": { - "smithy.api#required": true - } - } - } - }, - "example.weather#CitySummaries": { - "type": "list", - "member": { - "target": "example.weather#CitySummary" - } - }, - "example.weather#CitySummary": { - "type": "structure", - "members": { - "cityId": { - "target": "example.weather#CityId", - "traits": { - "smithy.api#required": true - } - }, - "name": { - "target": "smithy.api#String", - "traits": { - "smithy.api#required": true - } - } - } - } - } - } + @required + name: String, + } The ``ListCities`` operation is :ref:`paginated `, meaning the results of invoking the operation can be truncated, requiring subsequent @@ -679,70 +379,32 @@ property. The following operation gets the current time from the ``Weather`` service. -.. tabs:: - - .. code-tab:: smithy +.. code-block:: smithy - /// Provides weather forecasts. - @paginated(inputToken: "nextToken", outputToken: "nextToken", - pageSize: "pageSize") - service Weather { - version: "2006-03-01", - resources: [City], - operations: [GetCurrentTime] - } + /// Provides weather forecasts. + @paginated(inputToken: "nextToken", outputToken: "nextToken", + pageSize: "pageSize") + service Weather { + version: "2006-03-01", + resources: [City], + operations: [GetCurrentTime] + } - @readonly - operation GetCurrentTime { - output: GetCurrentTimeOutput - } + @readonly + operation GetCurrentTime { + input: GetCurrentTimeInput, + output: GetCurrentTimeOutput + } - structure GetCurrentTimeOutput { - @required - time: Timestamp - } + @input + structure GetCurrentTimeInput {} - .. code-tab:: json + @output + structure GetCurrentTimeOutput { + @required + time: Timestamp + } - { - "smithy": "1.0", - "shapes": { - "example.weather#Weather": { - "type": "service", - "version": "2006-03-01", - "resources": [ - { - "target": "example.weather#City" - } - ], - "operations": [ - { - "target": "example.weather#GetCurrentTime" - } - ] - }, - "example.weather#GetCurrentTime": { - "type": "operation", - "output": { - "target": "example.weather#GetCurrentTimeOutput" - }, - "traits": { - "smithy.api#readonly": true - } - }, - "example.weather#GetCurrentTimeOutput": { - "type": "structure", - "members": { - "time": { - "target": "smithy.api#Timestamp", - "traits": { - "smithy.api#required": true - } - } - } - } - } - } Building the Model ================== @@ -891,6 +553,7 @@ Finally, the complete ``weather.smithy`` model should look like: errors: [NoSuchResource] } + @input structure GetCityInput { // "cityId" provides the identifier for the resource and // has to be marked as required. @@ -898,6 +561,7 @@ Finally, the complete ``weather.smithy`` model should look like: cityId: CityId } + @output structure GetCityOutput { // "required" is used on output to indicate if the service // will always provide a value for the member. @@ -934,11 +598,13 @@ Finally, the complete ``weather.smithy`` model should look like: output: ListCitiesOutput } + @input structure ListCitiesInput { nextToken: String, pageSize: Integer } + @output structure ListCitiesOutput { nextToken: String, @@ -963,9 +629,14 @@ Finally, the complete ``weather.smithy`` model should look like: @readonly operation GetCurrentTime { + input: GetCurrentTimeInput, output: GetCurrentTimeOutput } + @input + structure GetCurrentTimeInput {} + + @output structure GetCurrentTimeOutput { @required time: Timestamp @@ -979,11 +650,13 @@ Finally, the complete ``weather.smithy`` model should look like: // "cityId" provides the only identifier for the resource since // a Forecast doesn't have its own. + @input structure GetForecastInput { @required cityId: CityId, } + @output structure GetForecastOutput { chanceOfRain: Float } @@ -1123,6 +796,9 @@ Finally, the complete ``weather.smithy`` model should look like: "smithy.api#required": true } } + }, + "traits": { + "smithy.api#input": true } }, "example.weather#GetCityOutput": { @@ -1140,10 +816,16 @@ Finally, the complete ``weather.smithy`` model should look like: "smithy.api#required": true } } + }, + "traits": { + "smithy.api#output": true } }, "example.weather#GetCurrentTime": { "type": "operation", + "input": { + "target": "example.weather#GetCurrentTimeInput" + }, "output": { "target": "example.weather#GetCurrentTimeOutput" }, @@ -1151,6 +833,12 @@ Finally, the complete ``weather.smithy`` model should look like: "smithy.api#readonly": true } }, + "example.weather#GetCurrentTimeInput": { + "type": "structure", + "traits": { + "smithy.api#input": true + } + }, "example.weather#GetCurrentTimeOutput": { "type": "structure", "members": { @@ -1160,6 +848,9 @@ Finally, the complete ``weather.smithy`` model should look like: "smithy.api#required": true } } + }, + "traits": { + "smithy.api#output": true } }, "example.weather#GetForecast": { @@ -1183,6 +874,9 @@ Finally, the complete ``weather.smithy`` model should look like: "smithy.api#required": true } } + }, + "traits": { + "smithy.api#input": true } }, "example.weather#GetForecastOutput": { @@ -1191,6 +885,9 @@ Finally, the complete ``weather.smithy`` model should look like: "chanceOfRain": { "target": "smithy.api#Float" } + }, + "traits": { + "smithy.api#output": true } }, "example.weather#ListCities": { @@ -1217,6 +914,9 @@ Finally, the complete ``weather.smithy`` model should look like: "pageSize": { "target": "smithy.api#Integer" } + }, + "traits": { + "smithy.api#input": true } }, "example.weather#ListCitiesOutput": { @@ -1231,6 +931,9 @@ Finally, the complete ``weather.smithy`` model should look like: "nextToken": { "target": "smithy.api#String" } + }, + "traits": { + "smithy.api#output": true } }, "example.weather#NoSuchResource": { diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cognito-user-pools-security.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cognito-user-pools-security.openapi.json index 3fef1412ca8..0a19bc9e541 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cognito-user-pools-security.openapi.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cognito-user-pools-security.openapi.json @@ -10,7 +10,7 @@ "operationId": "Operation1", "responses": { "200": { - "description": "Operation1 response" + "description": "Operation1 200 response" } } } diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-explicit-options.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-explicit-options.openapi.json index 4610e473f81..d4e234d6f29 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-explicit-options.openapi.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-explicit-options.openapi.json @@ -10,7 +10,7 @@ "operationId": "Foo", "responses": { "201": { - "description": "Foo response", + "description": "Foo 201 response", "headers": { "Access-Control-Allow-Origin": { "schema": { @@ -35,7 +35,7 @@ "statusCode": "200", "responseParameters": { "method.response.header.Access-Control-Allow-Origin": "'https://foo.com'", - "method.response.header.Access-Control-Expose-Headers": "'X-Amzn-Errortype,X-Amzn-Requestid'" + "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid'" } } } diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-model.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-model.openapi.json index 29a8b91c5a5..c83e75bcfe0 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-model.openapi.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-model.openapi.json @@ -119,7 +119,7 @@ ], "responses": { "204": { - "description": "DeletePayload response", + "description": "DeletePayload 204 response", "headers": { "Access-Control-Allow-Origin": { "schema": { @@ -144,7 +144,7 @@ "default": { "statusCode": "200", "responseParameters": { - "method.response.header.Access-Control-Expose-Headers": "'X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'", + "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'", "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'" } } @@ -320,7 +320,7 @@ ], "responses": { "201": { - "description": "PutPayload response", + "description": "PutPayload 201 response", "headers": { "Access-Control-Allow-Origin": { "schema": { @@ -345,7 +345,7 @@ "default": { "statusCode": "200", "responseParameters": { - "method.response.header.Access-Control-Expose-Headers": "'X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'", + "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'", "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'" } } diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-additional-headers.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-additional-headers.openapi.json index 6e8d1fab645..9cd903a3286 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-additional-headers.openapi.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-additional-headers.openapi.json @@ -119,7 +119,7 @@ ], "responses": { "204": { - "description": "DeletePayload response", + "description": "DeletePayload 204 response", "headers": { "Access-Control-Allow-Origin": { "schema": { @@ -144,7 +144,7 @@ "default": { "statusCode": "200", "responseParameters": { - "method.response.header.Access-Control-Expose-Headers": "'X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'", + "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'", "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'" } } @@ -320,7 +320,7 @@ ], "responses": { "201": { - "description": "PutPayload response", + "description": "PutPayload 201 response", "headers": { "Access-Control-Allow-Origin": { "schema": { @@ -345,7 +345,7 @@ "default": { "statusCode": "200", "responseParameters": { - "method.response.header.Access-Control-Expose-Headers": "'X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'", + "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'", "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'" } } diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-custom-gateway-response-headers.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-custom-gateway-response-headers.openapi.json index 58d3b3848cc..c7b8fe06c78 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-custom-gateway-response-headers.openapi.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-custom-gateway-response-headers.openapi.json @@ -10,7 +10,7 @@ "operationId": "Foo", "responses": { "201": { - "description": "Foo response", + "description": "Foo 201 response", "headers": { "Access-Control-Allow-Origin": { "schema": { @@ -35,7 +35,7 @@ "statusCode": "200", "responseParameters": { "method.response.header.Access-Control-Allow-Origin": "'https://foo.com'", - "method.response.header.Access-Control-Expose-Headers": "'X-Amzn-Errortype,X-Amzn-Requestid'" + "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid'" } } } diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/greedy-labels-for-rest.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/greedy-labels-for-rest.openapi.json index 6800752697f..6363d0b2bd5 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/greedy-labels-for-rest.openapi.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/greedy-labels-for-rest.openapi.json @@ -20,7 +20,7 @@ ], "responses": { "200": { - "description": "Operation response" + "description": "Operation 200 response" } } } diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/http-api-cors-wildcards.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/http-api-cors-wildcards.openapi.json index 91e3e96c978..139391837b8 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/http-api-cors-wildcards.openapi.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/http-api-cors-wildcards.openapi.json @@ -44,7 +44,7 @@ ], "responses": { "204": { - "description": "DeletePayload response" + "description": "DeletePayload 204 response" } }, "x-amazon-apigateway-integration": { @@ -140,7 +140,7 @@ ], "responses": { "201": { - "description": "PutPayload response" + "description": "PutPayload 201 response" } }, "x-amazon-apigateway-integration": { diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/http-api-cors.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/http-api-cors.openapi.json index f302b295faf..f7d6ea8a251 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/http-api-cors.openapi.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/http-api-cors.openapi.json @@ -44,7 +44,7 @@ ], "responses": { "204": { - "description": "DeletePayload response" + "description": "DeletePayload 204 response" } }, "x-amazon-apigateway-integration": { @@ -140,7 +140,7 @@ ], "responses": { "201": { - "description": "PutPayload response" + "description": "PutPayload 201 response" } }, "x-amazon-apigateway-integration": { diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/non-numeric-floats.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/non-numeric-floats.openapi.json index 157831cf3e5..db6bf1dfb56 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/non-numeric-floats.openapi.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/non-numeric-floats.openapi.json @@ -19,7 +19,7 @@ }, "responses": { "200": { - "description": "MyOperation response" + "description": "MyOperation 200 response" } } } diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/operation-http-api-key-security.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/operation-http-api-key-security.openapi.json index 1c1e55edbc9..710dd5aeaaa 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/operation-http-api-key-security.openapi.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/operation-http-api-key-security.openapi.json @@ -10,7 +10,7 @@ "operationId": "Operation1", "responses": { "200": { - "description": "Operation1 response" + "description": "Operation1 200 response" } }, "security": [ diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/substitution-not-performed.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/substitution-not-performed.json index b0ff8b33f83..d039eb2deb9 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/substitution-not-performed.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/substitution-not-performed.json @@ -10,7 +10,7 @@ "operationId": "MyOperation", "responses": { "200": { - "description": "MyOperation response" + "description": "MyOperation 200 response" } } } diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/substitution-performed.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/substitution-performed.json index d488cca1243..b18b54e597d 100644 --- a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/substitution-performed.json +++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/substitution-performed.json @@ -10,7 +10,7 @@ "operationId": "MyOperation", "responses": { "200": { - "description": "MyOperation response" + "description": "MyOperation 200 response" } } } diff --git a/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/valid-integration.json b/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/valid-integration.json index 430b10afb2b..557f4b0c360 100644 --- a/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/valid-integration.json +++ b/smithy-aws-apigateway-traits/src/test/resources/software/amazon/smithy/aws/apigateway/traits/errorfiles/valid-integration.json @@ -107,6 +107,24 @@ } } } + }, + "input": { + "target": "ns.foo#OperationInput" + }, + "output": { + "target": "ns.foo#OperationOutput" + } + }, + "ns.foo#OperationInput": { + "type": "structure", + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#OperationOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} } } } diff --git a/smithy-aws-cloudformation-traits/src/main/java/software/amazon/smithy/aws/cloudformation/traits/CfnResourceIndex.java b/smithy-aws-cloudformation-traits/src/main/java/software/amazon/smithy/aws/cloudformation/traits/CfnResourceIndex.java index c7b725aa349..b2a53d9252e 100644 --- a/smithy-aws-cloudformation-traits/src/main/java/software/amazon/smithy/aws/cloudformation/traits/CfnResourceIndex.java +++ b/smithy-aws-cloudformation-traits/src/main/java/software/amazon/smithy/aws/cloudformation/traits/CfnResourceIndex.java @@ -98,10 +98,10 @@ public CfnResourceIndex(Model model) { // Use the read lifecycle's input to collect the additional identifiers // and its output to collect readable properties. resource.getRead().ifPresent(operationId -> { - operationIndex.getInput(operationId).ifPresent(input -> { + operationIndex.getInputShape(operationId).ifPresent(input -> { addAdditionalIdentifiers(builder, computeResourceAdditionalIdentifiers(input)); }); - operationIndex.getOutput(operationId).ifPresent(output -> { + operationIndex.getOutputShape(operationId).ifPresent(output -> { updatePropertyMutabilities(builder, model, resourceId, operationId, output, SetUtils.of(Mutability.READ), this::addReadMutability); }); @@ -109,7 +109,7 @@ public CfnResourceIndex(Model model) { // Use the put lifecycle's input to collect put-able properties. resource.getPut().ifPresent(operationId -> { - operationIndex.getInput(operationId).ifPresent(input -> { + operationIndex.getInputShape(operationId).ifPresent(input -> { updatePropertyMutabilities(builder, model, resourceId, operationId, input, SetUtils.of(Mutability.CREATE, Mutability.WRITE), this::addPutMutability); }); @@ -117,7 +117,7 @@ public CfnResourceIndex(Model model) { // Use the create lifecycle's input to collect creatable properties. resource.getCreate().ifPresent(operationId -> { - operationIndex.getInput(operationId).ifPresent(input -> { + operationIndex.getInputShape(operationId).ifPresent(input -> { updatePropertyMutabilities(builder, model, resourceId, operationId, input, SetUtils.of(Mutability.CREATE), this::addCreateMutability); }); @@ -125,7 +125,7 @@ public CfnResourceIndex(Model model) { // Use the update lifecycle's input to collect writeable properties. resource.getUpdate().ifPresent(operationId -> { - operationIndex.getInput(operationId).ifPresent(input -> { + operationIndex.getInputShape(operationId).ifPresent(input -> { updatePropertyMutabilities(builder, model, resourceId, operationId, input, SetUtils.of(Mutability.WRITE), this::addWriteMutability); }); diff --git a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/additionalschemas-conflict.smithy b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/additionalschemas-conflict.smithy index c598d8cc7f4..bded4cb0515 100644 --- a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/additionalschemas-conflict.smithy +++ b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/additionalschemas-conflict.smithy @@ -21,8 +21,10 @@ resource AdditionalSchemasConflictResource { operation CreateAdditionalSchemasConflictResource { input: CreateAdditionalSchemasConflictResourceRequest, + output: CreateAdditionalSchemasConflictResourceResponse } +@input structure CreateAdditionalSchemasConflictResourceRequest { bar: String, } @@ -30,3 +32,6 @@ structure CreateAdditionalSchemasConflictResourceRequest { structure AdditionalSchemasConflictProperties { bar: Boolean, } + +@output +structure CreateAdditionalSchemasConflictResourceResponse {} diff --git a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/deconflict-by-excluding.smithy b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/deconflict-by-excluding.smithy index 98c1c9b7d57..b7f99d29806 100644 --- a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/deconflict-by-excluding.smithy +++ b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/deconflict-by-excluding.smithy @@ -22,8 +22,10 @@ resource AdditionalSchemasDeconflictedResource { operation CreateAdditionalSchemasDeconflictedResource { input: CreateAdditionalSchemasDeconflictedResourceRequest, + output: CreateAdditionalSchemasDeconflictedResourceResponse } +@input structure CreateAdditionalSchemasDeconflictedResourceRequest { @cfnExcludeProperty bar: String, @@ -32,3 +34,6 @@ structure CreateAdditionalSchemasDeconflictedResourceRequest { structure AdditionalSchemasDeconflictedProperties { bar: Boolean, } + +@output +structure CreateAdditionalSchemasDeconflictedResourceResponse {} diff --git a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/lifecycle-conflict.smithy b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/lifecycle-conflict.smithy index f1f1d8d54e4..1539cf70432 100644 --- a/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/lifecycle-conflict.smithy +++ b/smithy-aws-cloudformation-traits/src/test/resources/software/amazon/smithy/aws/cloudformation/traits/errorfiles/lifecycle-conflict.smithy @@ -22,23 +22,30 @@ resource LifecycleConflictResource { operation CreateLifecycleConflictResource { input: CreateLifecycleConflictResourceRequest, + output: CreateLifecycleConflictResourceResponse } +@input structure CreateLifecycleConflictResourceRequest { bar: String, } +@output +structure CreateLifecycleConflictResourceResponse {} + @readonly operation GetLifecycleConflictResource { input: GetLifecycleConflictResourceRequest, output: GetLifecycleConflictResourceResponse, } +@input structure GetLifecycleConflictResourceRequest { @required fooId: String, } +@output structure GetLifecycleConflictResourceResponse { bar: Boolean, } diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/empty-input-output.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/empty-input-output.smithy index cc98fe692d1..599212e86a8 100644 --- a/smithy-aws-protocol-tests/model/awsJson1_0/empty-input-output.smithy +++ b/smithy-aws-protocol-tests/model/awsJson1_0/empty-input-output.smithy @@ -12,6 +12,7 @@ use smithy.test#httpResponseTests /// The example tests how requests and responses are serialized when there's /// no request or response payload because the operation has no input or output. /// While this should be rare, code generators must support this. +@suppress(["OperationMissingInput", "OperationMissingOutput"]) operation NoInputAndNoOutput {} apply NoInputAndNoOutput @httpRequestTests([ @@ -113,6 +114,7 @@ apply NoInputAndNoOutput @httpResponseTests([ /// no request or response payload because the operation has no input and the /// output is empty. While this should be rare, code generators must support /// this. +@suppress(["OperationMissingInput"]) operation NoInputAndOutput { output: NoInputAndOutputOutput } @@ -147,6 +149,7 @@ apply NoInputAndOutput @httpResponseTests([ } ]) +@output structure NoInputAndOutputOutput {} /// The example tests how requests and responses are serialized when there's @@ -155,7 +158,7 @@ structure NoInputAndOutputOutput {} /// be rare, code generators must support this. operation EmptyInputAndEmptyOutput { input: EmptyInputAndEmptyOutputInput, - output: EmptyInputAndEmptyOutputInput + output: EmptyInputAndEmptyOutputOutput } apply EmptyInputAndEmptyOutput @httpRequestTests([ @@ -188,4 +191,8 @@ apply EmptyInputAndEmptyOutput @httpResponseTests([ }, ]) +@input structure EmptyInputAndEmptyOutputInput {} + +@output +structure EmptyInputAndEmptyOutputOutput {} diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/endpoint-paths.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/endpoint-paths.smithy index 3276eb52eb3..176e62678d2 100644 --- a/smithy-aws-protocol-tests/model/awsJson1_0/endpoint-paths.smithy +++ b/smithy-aws-protocol-tests/model/awsJson1_0/endpoint-paths.smithy @@ -20,5 +20,5 @@ use smithy.test#httpRequestTests appliesTo: "client" } ]) - +@suppress(["OperationMissingInput", "OperationMissingOutput"]) operation HostWithPathOperation {} diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/endpoints.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/endpoints.smithy index 2cb6d938204..3da9b054eba 100644 --- a/smithy-aws-protocol-tests/model/awsJson1_0/endpoints.smithy +++ b/smithy-aws-protocol-tests/model/awsJson1_0/endpoints.smithy @@ -23,6 +23,7 @@ use smithy.test#httpRequestTests } ]) @endpoint(hostPrefix: "foo.") +@suppress(["OperationMissingInput", "OperationMissingOutput"]) operation EndpointOperation {} @@ -46,11 +47,13 @@ operation EndpointOperation {} } ]) @endpoint(hostPrefix: "foo.{label}.") +@suppress(["OperationMissingOutput"]) operation EndpointWithHostLabelOperation { - input: HostLabelInput, + input: EndpointWithHostLabelOperationInput, } -structure HostLabelInput { +@input +structure EndpointWithHostLabelOperationInput { @required @hostLabel label: String, diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/errors.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/errors.smithy index 495528f10d5..29eff31282b 100644 --- a/smithy-aws-protocol-tests/model/awsJson1_0/errors.smithy +++ b/smithy-aws-protocol-tests/model/awsJson1_0/errors.smithy @@ -18,10 +18,17 @@ use smithy.test#httpResponseTests /// properly deserialize successful and error responses. @idempotent operation GreetingWithErrors { + input: GreetingWithErrorsInput, output: GreetingWithErrorsOutput, errors: [InvalidGreeting, ComplexError, FooError] } +@input +structure GreetingWithErrorsInput { + greeting: String, +} + +@output structure GreetingWithErrorsOutput { greeting: String, } diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/json-structs.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/json-structs.smithy index 75ee5885c18..55c93b36d3d 100644 --- a/smithy-aws-protocol-tests/model/awsJson1_0/json-structs.smithy +++ b/smithy-aws-protocol-tests/model/awsJson1_0/json-structs.smithy @@ -136,11 +136,18 @@ apply SimpleScalarProperties @httpResponseTests([ // This example serializes simple scalar types in the top level JSON document. operation SimpleScalarProperties { - input: SimpleScalarPropertiesInputOutput, - output: SimpleScalarPropertiesInputOutput + input: SimpleScalarPropertiesInput, + output: SimpleScalarPropertiesOutput } -structure SimpleScalarPropertiesInputOutput { +@input +structure SimpleScalarPropertiesInput { + floatValue: Float, + doubleValue: Double, +} + +@output +structure SimpleScalarPropertiesOutput { floatValue: Float, doubleValue: Double, } diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/unions.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/unions.smithy index a397a348800..3b8ff14b169 100644 --- a/smithy-aws-protocol-tests/model/awsJson1_0/unions.smithy +++ b/smithy-aws-protocol-tests/model/awsJson1_0/unions.smithy @@ -16,12 +16,17 @@ use aws.protocoltests.shared#FooEnum /// This operation uses unions for inputs and outputs. @idempotent operation JsonUnions { - input: UnionInputOutput, - output: UnionInputOutput, + input: JsonUnionsInput, + output: JsonUnionsOutput, } -/// A shared structure that contains a single union member. -structure UnionInputOutput { +@input +structure JsonUnionsInput { + contents: MyUnion +} + +@output +structure JsonUnionsOutput { contents: MyUnion } diff --git a/smithy-aws-protocol-tests/model/awsJson1_1/optional-input-output.smithy b/smithy-aws-protocol-tests/model/awsJson1_1/optional-input-output.smithy index e22a972be61..fe80fa155fb 100644 --- a/smithy-aws-protocol-tests/model/awsJson1_1/optional-input-output.smithy +++ b/smithy-aws-protocol-tests/model/awsJson1_1/optional-input-output.smithy @@ -38,6 +38,16 @@ use smithy.test#httpResponseTests }, ]) operation OperationWithOptionalInputOutput { - input: SimpleStruct, - output: SimpleStruct, + input: OperationWithOptionalInputOutputInput, + output: OperationWithOptionalInputOutputOutput, +} + +@input +structure OperationWithOptionalInputOutputInput { + Value: String +} + +@output +structure OperationWithOptionalInputOutputOutput { + Value: String } diff --git a/smithy-aws-protocol-tests/model/awsJson1_1/services/machinelearning.smithy b/smithy-aws-protocol-tests/model/awsJson1_1/services/machinelearning.smithy index a2b357f81b4..58f3264234b 100644 --- a/smithy-aws-protocol-tests/model/awsJson1_1/services/machinelearning.smithy +++ b/smithy-aws-protocol-tests/model/awsJson1_1/services/machinelearning.smithy @@ -85,6 +85,7 @@ structure LimitExceededException { code: ErrorCode, } +@input structure PredictInput { @required MLModelId: EntityId, @@ -107,6 +108,7 @@ structure PredictorNotMountedException { message: ErrorMessage, } +@output structure PredictOutput { Prediction: Prediction, } diff --git a/smithy-aws-protocol-tests/model/awsQuery/empty-input-output.smithy b/smithy-aws-protocol-tests/model/awsQuery/empty-input-output.smithy index e833e62fa36..9a75698aded 100644 --- a/smithy-aws-protocol-tests/model/awsQuery/empty-input-output.smithy +++ b/smithy-aws-protocol-tests/model/awsQuery/empty-input-output.smithy @@ -44,7 +44,8 @@ apply NoInputAndNoOutput @httpResponseTests([ /// /// While this should be rare, code generators must support this. operation NoInputAndOutput { - input: NoInputAndOutputOutput + input: NoInputAndOutputInput, + output: NoInputAndOutputOutput } apply NoInputAndOutput @httpRequestTests([ @@ -71,6 +72,10 @@ apply NoInputAndOutput @httpResponseTests([ } ]) +@input +structure NoInputAndOutputInput {} + +@output structure NoInputAndOutputOutput {} /// The example tests how requests and responses are serialized when there's diff --git a/smithy-aws-protocol-tests/model/restJson1/empty-input-output.smithy b/smithy-aws-protocol-tests/model/restJson1/empty-input-output.smithy index 5c9271b68f4..7c0b1e7a94d 100644 --- a/smithy-aws-protocol-tests/model/restJson1/empty-input-output.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/empty-input-output.smithy @@ -41,7 +41,7 @@ apply NoInputAndNoOutput @httpRequestTests([ headers: { "Accept": "application/json" }, - appliesTo: "server", + appliesTo: "server", } ]) @@ -58,6 +58,54 @@ apply NoInputAndNoOutput @httpResponseTests([ } ]) +/// This test is similar to NoInputAndNoOutput, but uses explicit Unit types. +@http(uri: "/UnitInputAndOutput", method: "POST") +operation UnitInputAndOutput { + input: Unit, + output: Unit +} + +apply UnitInputAndOutput @httpRequestTests([ + { + id: "RestJsonUnitInputAndOutput", + documentation: """ + A unit type input serializes no payload. When clients do not + need to serialize any data in the payload, they should omit + a payload altogether.""", + protocol: restJson1, + method: "POST", + uri: "/UnitInputAndOutput", + body: "" + }, + { + id: "RestJsonUnitInputAllowsAccept", + documentation: """ + Servers should allow the accept header to be set to the + default content-type.""", + protocol: restJson1, + method: "POST", + uri: "/UnitInputAndOutput", + body: "", + headers: { + "Accept": "application/json" + }, + appliesTo: "server", + } +]) + +apply NoInputAndNoOutput @httpResponseTests([ + { + id: "RestJsonUnitInputAndOutputNoOutput", + documentation: """ + When an operation defines Unit output, the service will respond + with an empty payload, and may optionally include the content-type + header.""", + protocol: restJson1, + code: 200, + body: "" + } +]) + /// The example tests how requests and responses are serialized when there's /// no request or response payload because the operation has no input and the /// output is empty. While this should be rare, code generators must support @@ -122,6 +170,7 @@ apply NoInputAndOutput @httpResponseTests([ } ]) +@output structure NoInputAndOutputOutput {} /// The example tests how requests and responses are serialized when there's @@ -190,5 +239,8 @@ apply EmptyInputAndEmptyOutput @httpResponseTests([ }, ]) +@input structure EmptyInputAndEmptyOutputInput {} + +@output structure EmptyInputAndEmptyOutputOutput {} diff --git a/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy b/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy index dec400408f5..3f02c6412bd 100644 --- a/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy @@ -15,8 +15,8 @@ use aws.protocoltests.shared#StringMap @http(uri: "/HttpPrefixHeaders", method: "GET") @externalDocumentation("httpPrefixHeaders Trait": "https://awslabs.github.io/smithy/1.0/spec/http.html#httpprefixheaders-trait") operation HttpPrefixHeaders { - input: HttpPrefixHeadersInputOutput, - output: HttpPrefixHeadersInputOutput + input: HttpPrefixHeadersInput, + output: HttpPrefixHeadersOutput } apply HttpPrefixHeaders @httpRequestTests([ @@ -78,7 +78,17 @@ apply HttpPrefixHeaders @httpResponseTests([ }, ]) -structure HttpPrefixHeadersInputOutput { +@input +structure HttpPrefixHeadersInput { + @httpHeader("X-Foo") + foo: String, + + @httpPrefixHeaders("X-Foo-") + fooMap: StringMap, +} + +@output +structure HttpPrefixHeadersOutput { @httpHeader("X-Foo") foo: String, @@ -89,11 +99,12 @@ structure HttpPrefixHeadersInputOutput { /// Clients that perform this test extract all headers from the response. @readonly @http(uri: "/HttpPrefixHeadersResponse", method: "GET") -operation HttpPrefixHeadersResponse { - output: HttpPrefixHeadersResponseOutput +operation HttpPrefixHeadersInResponse { + input: HttpPrefixHeadersInResponseInput, + output: HttpPrefixHeadersInResponseOutput } -apply HttpPrefixHeadersResponse @httpResponseTests([ +apply HttpPrefixHeadersInResponse @httpResponseTests([ { id: "HttpPrefixHeadersResponse", documentation: "(de)serializes all response headers", @@ -112,7 +123,11 @@ apply HttpPrefixHeadersResponse @httpResponseTests([ }, ]) -structure HttpPrefixHeadersResponseOutput { +@input +structure HttpPrefixHeadersInResponseInput {} + +@output +structure HttpPrefixHeadersInResponseOutput { @httpPrefixHeaders("") prefixHeaders: StringMap, } diff --git a/smithy-aws-protocol-tests/model/restJson1/main.smithy b/smithy-aws-protocol-tests/model/restJson1/main.smithy index 070f29e6cbd..290d2daf6ae 100644 --- a/smithy-aws-protocol-tests/model/restJson1/main.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/main.smithy @@ -21,6 +21,7 @@ service RestJson { NoInputAndNoOutput, NoInputAndOutput, EmptyInputAndEmptyOutput, + UnitInputAndOutput, // @httpHeader tests InputAndOutputWithHeaders, @@ -47,7 +48,7 @@ service RestJson { // @httpPrefixHeaders tests HttpPrefixHeaders, - HttpPrefixHeadersResponse, + HttpPrefixHeadersInResponse, // @httpPayload tests HttpPayloadTraits, @@ -82,6 +83,7 @@ service RestJson { // Unions JsonUnions, + PostPlayerAction, // @endpoint and @hostLabel trait tests EndpointOperation, diff --git a/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-accept.smithy b/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-accept.smithy index 2ff8df9fe3a..363ea09205f 100644 --- a/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-accept.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-accept.smithy @@ -89,10 +89,10 @@ operation MalformedAcceptWithBody { @suppress(["UnstableTrait"]) @http(method: "POST", uri: "/MalformedAcceptWithPayload") operation MalformedAcceptWithPayload { - output: MalformedAcceptWithPayloadInput + output: MalformedAcceptWithPayloadOutput } -structure MalformedAcceptWithPayloadInput { +structure MalformedAcceptWithPayloadOutput { @httpPayload payload: JpegBlob } diff --git a/smithy-aws-protocol-tests/model/restJson1/unions.smithy b/smithy-aws-protocol-tests/model/restJson1/unions.smithy index 7ee9fe3efca..e5b1dda760c 100644 --- a/smithy-aws-protocol-tests/model/restJson1/unions.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/unions.smithy @@ -443,3 +443,73 @@ apply JsonUnions @httpResponseTests([ } }, ]) + + +/// This operation defines a union with a Unit member. +@http(uri: "/PostPlayerInput", method: "POST") +operation PostPlayerAction { + input: PostPlayerActionInput, + output: PostPlayerActionOutput +} + +@input +structure PostPlayerActionInput { + @required + action: PlayerAction +} + +@output +structure PostPlayerActionOutput { + @required + action: PlayerAction +} + +union PlayerAction { + /// Quit the game. + quit: Unit +} + +apply PostPlayerAction @httpRequestTests([ + { + id: "RestJsonInputUnionWithUnitMember", + documentation: "Unit types in unions are serialized like normal structures in requests.", + protocol: restJson1, + method: "PUT", + "uri": "/MovePlayer", + body: """ + { + "action": { + "quit": {} + } + }""", + bodyMediaType: "application/json", + headers: {"Content-Type": "application/json"}, + params: { + action: { + quit: {} + } + } + } +]) + +apply PostPlayerAction @httpResponseTests([ + { + id: "RestJsonOutputUnionWithUnitMember", + documentation: "Unit types in unions are serialized like normal structures in responses.", + protocol: restJson1, + code: 200, + body: """ + { + "action": { + "quit": {} + } + }""", + bodyMediaType: "application/json", + headers: {"Content-Type": "application/json"}, + params: { + action: { + quit: {} + } + } + } +]) diff --git a/smithy-aws-protocol-tests/model/shared-types.smithy b/smithy-aws-protocol-tests/model/shared-types.smithy index f2b3c8256d8..b2e25f79e6b 100644 --- a/smithy-aws-protocol-tests/model/shared-types.smithy +++ b/smithy-aws-protocol-tests/model/shared-types.smithy @@ -7,15 +7,48 @@ $version: "1.0" -metadata suppressions = [{ - id: "DeprecatedTrait", - namespace: "*", - reason: """ - Some of the AWS protocols make use of deprecated traits, and some are - themselves deprecated traits. As this package is intended to test those - protocols, the warnings should be suppressed. - """ -}] +metadata suppressions = [ + { + id: "DeprecatedTrait", + namespace: "*", + reason: """ + Some of the AWS protocols make use of deprecated traits, and some are + themselves deprecated traits. As this package is intended to test those + protocols, the warnings should be suppressed.""" + }, + // TODO: The following suppressions are temporary until protocol tests are updated + // to use dedicated input and output shapes, the `@input` and `@output` traits, + // and suppressions as necessary. This will be easier to do when mixins are + // launched as part of Smithy IDL 2.0. + { + id: "OperationMissingInputTrait", + namespace: "*", + reason: """ + This is a temporary solution until we rewrite these tests to define input, output, + and use the `@input` and `@output` traits.""" + }, + { + id: "OperationMissingOutputTrait", + namespace: "*", + reason: """ + This is a temporary solution until we rewrite these tests to define input, output, + and use the `@input` and `@output` traits.""" + }, + { + id: "OperationMissingInput", + namespace: "*", + reason: """ + This is a temporary solution until we rewrite these tests to define input, output, + and use the `@input` and `@output` traits.""" + }, + { + id: "OperationMissingOutput", + namespace: "*", + reason: """ + This is a temporary solution until we rewrite these tests to define input, output, + and use the `@input` and `@output` traits.""" + } +] namespace aws.protocoltests.shared diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/HttpChecksumTraitValidator.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/HttpChecksumTraitValidator.java index 625151a6698..bac173704bc 100644 --- a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/HttpChecksumTraitValidator.java +++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/HttpChecksumTraitValidator.java @@ -24,6 +24,7 @@ import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.traits.EnumTrait; @@ -85,9 +86,7 @@ private List validateOperation(Model model, OperationShape oper List events = new ArrayList<>(); // TraitTarget validation will raise an error if there's no operation input. - operation.getInput().ifPresent(inputId -> { - StructureShape input = model.expectShape(inputId, StructureShape.class); - + model.getShape(operation.getInputShape()).flatMap(Shape::asStructureShape).ifPresent(input -> { if (isRequestChecksumConfiguration) { events.addAll(validateRequestChecksumConfiguration(model, trait, operation, input)); } @@ -219,13 +218,9 @@ private List validateResponseChecksumConfiguration( List events = new ArrayList<>(); // Check for header binding conflicts with the output shape. - if (operation.getOutput().isPresent()) { - StructureShape outputShape = model.expectShape(operation.getOutput().get(), StructureShape.class); + model.getShape(operation.getOutputShape()).flatMap(Shape::asStructureShape).ifPresent(outputShape -> { events.addAll(validateHeaderConflicts(operation, outputShape)); - } else { - events.add(error(operation, trait, "The `httpChecksum` trait defines `response` checksum behavior but the" - + " operation does not have output.")); - } + }); // Validate requestValidationModeMember is set properly for response behavior. if (!trait.getRequestValidationModeMember().isPresent()) { diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/CleanClientDiscoveryTraitTransformer.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/CleanClientDiscoveryTraitTransformer.java index ad37a182562..c6789a83831 100644 --- a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/CleanClientDiscoveryTraitTransformer.java +++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/CleanClientDiscoveryTraitTransformer.java @@ -108,8 +108,7 @@ private Set getMembersToUpdate(Model model, Set updatedOperation // Filter out the ones which are having their endpoint discovery traits removed .filter(operation -> !updatedOperations.contains(operation.getId())) // Get the input shapes of those operations - .filter(operation -> operation.getInput().isPresent()) - .map(operation -> model.getShape(operation.getInput().get()).flatMap(Shape::asStructureShape)) + .map(operation -> model.getShape(operation.getInputShape()).flatMap(Shape::asStructureShape)) .filter(Optional::isPresent) // Get the input members .flatMap(input -> input.get().getAllMembers().values().stream()) diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryIndex.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryIndex.java index f8c9b23d56f..9be5bc4d355 100644 --- a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryIndex.java +++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryIndex.java @@ -93,9 +93,11 @@ private Map getOperations( private List getDiscoveryIds(OperationIndex opIndex, OperationShape operation) { List discoveryIds = new ArrayList<>(); - opIndex.getInput(operation).ifPresent(input -> input.getAllMembers().values().stream() - .filter(member -> member.hasTrait(ClientEndpointDiscoveryIdTrait.class)) - .forEach(discoveryIds::add)); + for (MemberShape member : opIndex.expectInputShape(operation).getAllMembers().values()) { + if (member.hasTrait(ClientEndpointDiscoveryIdTrait.class)) { + discoveryIds.add(member); + } + } return discoveryIds; } diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryValidator.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryValidator.java index 395844c4f4e..edd0608a531 100644 --- a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryValidator.java +++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryValidator.java @@ -151,20 +151,13 @@ private List validateEndpointOperation( Model model, OperationIndex opIndex, OperationShape operation ) { List events = new ArrayList<>(); - opIndex.getInput(operation) - .map(input -> validateEndpointOperationInput(model, input, operation)) - .ifPresent(events::addAll); - - Optional output = opIndex.getOutput(operation); - if (!output.isPresent()) { - events.add(error(operation, "Endpoint discovery operations must have an output.")); - return events; - } + events.addAll(validateEndpointOperationInput(model, opIndex.expectInputShape(operation), operation)); + StructureShape output = opIndex.expectOutputShape(operation); - Map outputMembers = output.get().getAllMembers(); + Map outputMembers = output.getAllMembers(); if (outputMembers.size() != 1 || !outputMembers.containsKey("Endpoints")) { - events.add(error(output.get(), String.format( - "Endpoint discovery operation output may only contain an `Endpoints` member, but found: %s", + events.add(error(operation, String.format( + "Endpoint discovery operation output must only contain an `Endpoints` member, but found: [%s]", String.join(",", outputMembers.keySet()) ))); } diff --git a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json index e4dce9f674b..769751353a0 100644 --- a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json +++ b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json @@ -248,7 +248,7 @@ }, "traits": { "smithy.api#trait": { - "selector": "operation :test(-[input]->)" + "selector": "operation" }, "smithy.api#documentation": "Indicates that an operation supports checksum validation.", "smithy.api#unstable": {} diff --git a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryIdTraitTest.java b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryIdTraitTest.java index 2309da7e405..d24f89674d7 100644 --- a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryIdTraitTest.java +++ b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryIdTraitTest.java @@ -44,7 +44,7 @@ public void loadsFromModel() { .expectShape(ShapeId.from("ns.foo#GetObject")) .asOperationShape().get(); MemberShape member = result - .getShape(operation.getInput().get()).get() + .getShape(operation.getInputShape()).get() .asStructureShape().get() .getMember("Id").get(); diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/clientendpointdiscovery/test-model.json b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/clientendpointdiscovery/test-model.json index a8fc832904d..79f2de52e2b 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/clientendpointdiscovery/test-model.json +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/clientendpointdiscovery/test-model.json @@ -49,6 +49,9 @@ "Identifiers": { "target": "ns.foo#Identifiers" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#Identifiers": { @@ -66,6 +69,9 @@ "Endpoints": { "target": "ns.foo#Endpoints" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#Endpoints": { @@ -114,6 +120,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#GetObjectOutput": { @@ -122,6 +131,9 @@ "Object": { "target": "smithy.api#Blob" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#PutObject": { @@ -129,6 +141,9 @@ "input": { "target": "ns.foo#PutObjectInput" }, + "output": { + "target": "ns.foo#PutObjectOutput" + }, "errors": [ { "target": "ns.foo#InvalidEndpointError" @@ -153,6 +168,15 @@ "Object": { "target": "smithy.api#Blob" } + }, + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#PutObjectOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} } }, "ns.foo#InvalidEndpointError": { diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-error.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-error.smithy index a2f899403c1..55bde5a2ef1 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-error.smithy +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-error.smithy @@ -40,6 +40,7 @@ operation DescribeEndpoints { output: DescribeEndpointsOutput, } +@input structure DescribeEndpointsInput { Operation: String, Identifiers: Identifiers, @@ -50,6 +51,7 @@ map Identifiers { value: String, } +@output structure DescribeEndpointsOutput { Endpoints: Endpoints, } @@ -69,22 +71,35 @@ operation GetObject { output: GetObjectOutput, } +@input structure GetObjectInput { @required Id: String, } +@output structure GetObjectOutput { Object: Blob, } @clientDiscoveredEndpoint(required: true) operation GetObjectWithEndpointError { - input: GetObjectInput, - output: GetObjectOutput, + input: GetObjectWithEndpointErrorInput, + output: GetObjectWithEndpointErrorOutput, errors: [InvalidEndpointError], } +@input +structure GetObjectWithEndpointErrorInput { + @required + Id: String, +} + +@output +structure GetObjectWithEndpointErrorOutput { + Object: Blob, +} + @error("client") @httpError(421) structure InvalidEndpointError {} diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-shape-not-structure.json b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-shape-not-structure.json index ad8dbfffe62..c69080704d0 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-shape-not-structure.json +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-shape-not-structure.json @@ -37,6 +37,9 @@ "Identifiers": { "target": "ns.foo#Identifiers" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#Identifiers": { @@ -54,6 +57,9 @@ "Endpoints": { "target": "ns.foo#Endpoints" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#Endpoints": { @@ -100,6 +106,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#GetObjectOutput": { @@ -108,6 +117,9 @@ "Object": { "target": "smithy.api#Blob" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#InvalidEndpointError": { diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoints-not-list.json b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoints-not-list.json index 06a81302435..f1e33e737a0 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoints-not-list.json +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoints-not-list.json @@ -37,6 +37,9 @@ "Identifiers": { "target": "ns.foo#Identifiers" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#Identifiers": { @@ -54,6 +57,9 @@ "Endpoints": { "target": "ns.foo#Endpoints" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#Endpoints": { @@ -94,6 +100,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#GetObjectOutput": { @@ -102,6 +111,9 @@ "Object": { "target": "smithy.api#Blob" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#InvalidEndpointError": { diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-endpoint-structure.json b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-endpoint-structure.json index 246bc19e98e..beb8fd9bb25 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-endpoint-structure.json +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-endpoint-structure.json @@ -37,6 +37,9 @@ "Identifiers": { "target": "ns.foo#Identifiers" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#Identifiers": { @@ -54,6 +57,9 @@ "Endpoints": { "target": "ns.foo#Endpoints" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#Endpoints": { @@ -105,6 +111,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#GetObjectOutput": { @@ -113,6 +122,9 @@ "Object": { "target": "smithy.api#Blob" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#InvalidEndpointError": { diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-input-shape.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-input-shape.errors index 3bbe3c17739..65bb574e936 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-input-shape.errors +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-input-shape.errors @@ -1,4 +1,4 @@ [ERROR] ns.foo#DescribeEndpointsInput$Identifiers: The Identifiers member of an endpoint discovery operation must be a map whose keys and values are strings. | ClientEndpointDiscovery [ERROR] ns.foo#DescribeEndpointsInput: Input for endpoint discovery operation `ns.foo#DescribeEndpoints` may only have the members Operation and Identifiers but found: Operation, Identifiers, TTL | ClientEndpointDiscovery [ERROR] smithy.api#Blob: The Operation member of an endpoint discovery operation must be a string | ClientEndpointDiscovery -[ERROR] ns.foo#DescribeEndpointsInput2$Identifiers: The Identifiers member of an endpoint discovery operation must be a map whose keys and values are strings. | ClientEndpointDiscovery \ No newline at end of file +[ERROR] ns.foo#DescribeEndpoints2Input$Identifiers: The Identifiers member of an endpoint discovery operation must be a map whose keys and values are strings. | ClientEndpointDiscovery diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-input-shape.json b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-input-shape.json index 96783613505..debe8067252 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-input-shape.json +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/invalid-input-shape.json @@ -49,10 +49,10 @@ "ns.foo#DescribeEndpoints2": { "type": "operation", "input": { - "target": "ns.foo#DescribeEndpointsInput2" + "target": "ns.foo#DescribeEndpoints2Input" }, "output": { - "target": "ns.foo#DescribeEndpointsOutput" + "target": "ns.foo#DescribeEndpoints2Output" } }, "ns.foo#DescribeEndpointsInput": { @@ -67,9 +67,12 @@ "Identifiers": { "target": "ns.foo#Identifiers" } + }, + "traits": { + "smithy.api#input": {} } }, - "ns.foo#DescribeEndpointsInput2": { + "ns.foo#DescribeEndpoints2Input": { "type": "structure", "members": { "Operation": { @@ -78,6 +81,9 @@ "Identifiers": { "target": "ns.foo#StructureIdentifiers" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#Identifiers": { @@ -103,6 +109,20 @@ "Endpoints": { "target": "ns.foo#Endpoints" } + }, + "traits": { + "smithy.api#output": {} + } + }, + "ns.foo#DescribeEndpoints2Output": { + "type": "structure", + "members": { + "Endpoints": { + "target": "ns.foo#Endpoints" + } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#Endpoints": { @@ -151,6 +171,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#GetObjectOutput": { @@ -159,6 +182,9 @@ "Object": { "target": "smithy.api#Blob" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#InvalidEndpointError": { diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-endpoints-member.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-endpoints-member.errors index f475e03911f..da277cc4713 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-endpoints-member.errors +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-endpoints-member.errors @@ -1 +1 @@ -[ERROR] ns.foo#DescribeEndpointsOutput: Endpoint discovery operation output may only contain an `Endpoints` member, but found: Addresses | ClientEndpointDiscovery \ No newline at end of file +[ERROR] ns.foo#DescribeEndpoints: Endpoint discovery operation output must only contain an `Endpoints` member, but found: [Addresses] | ClientEndpointDiscovery diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-endpoints-member.json b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-endpoints-member.json index 168a423540c..bcff7985355 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-endpoints-member.json +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-endpoints-member.json @@ -37,6 +37,9 @@ "Identifiers": { "target": "ns.foo#Identifiers" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#Identifiers": { @@ -54,6 +57,9 @@ "Addresses": { "target": "ns.foo#Endpoints" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#Endpoints": { @@ -102,6 +108,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#GetObjectOutput": { @@ -110,6 +119,9 @@ "Object": { "target": "smithy.api#Blob" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#InvalidEndpointError": { diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-input.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-input.errors index e69de29bb2d..ad9885a1126 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-input.errors +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-input.errors @@ -0,0 +1 @@ +[WARNING] ns.foo#PutObject: This operation does not define output | OperationMissingOutput diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-input.json b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-input.json index ab5d9e9c7f7..576a78bab85 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-input.json +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-input.json @@ -41,16 +41,22 @@ } }, "ns.foo#DescribeEndpointsInput": { - "type": "structure" - }, + "type": "structure", + "traits": { + "smithy.api#input": {} + } + }, "ns.foo#DescribeEndpointsOutput": { "type": "structure", "members": { "Endpoints": { "target": "ns.foo#Endpoints" } + }, + "traits": { + "smithy.api#output": {} } - }, + }, "ns.foo#Endpoints": { "type": "list", "member": { @@ -96,16 +102,22 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} } - }, + }, "ns.foo#GetObjectOutput": { "type": "structure", "members": { "Object": { "target": "smithy.api#Blob" } + }, + "traits": { + "smithy.api#output": {} } - }, + }, "ns.foo#PutObject": { "type": "operation", "input": { @@ -134,8 +146,11 @@ "Object": { "target": "smithy.api#Blob" } + }, + "traits": { + "smithy.api#input": {} } - }, + }, "ns.foo#InvalidEndpointError": { "type": "structure", "traits": { diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-output.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-output.errors index 45ea4d74fb5..35a438c7cb7 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-output.errors +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-output.errors @@ -1 +1,2 @@ -[ERROR] ns.foo#DescribeEndpoints: Endpoint discovery operations must have an output. | ClientEndpointDiscovery \ No newline at end of file +[WARNING] ns.foo#DescribeEndpoints: This operation does not define output | OperationMissingOutput +[ERROR] ns.foo#DescribeEndpoints: Endpoint discovery operation output must only contain an `Endpoints` member, but found: [] | ClientEndpointDiscovery diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-output.json b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-output.json index 7fee01cfa3f..adcd89082c8 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-output.json +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-output.json @@ -34,6 +34,9 @@ "Identifiers": { "target": "ns.foo#Identifiers" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#Identifiers": { @@ -74,6 +77,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#GetObjectOutput": { @@ -82,6 +88,9 @@ "Object": { "target": "smithy.api#Blob" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#InvalidEndpointError": { diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-header-conflicts.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-header-conflicts.smithy index 7fc8a772408..b5e1355ce8e 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-header-conflicts.smithy +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-header-conflicts.smithy @@ -45,6 +45,7 @@ operation NoConflicts { } +@input structure HeaderConflictsInput { @httpHeader("x-amz-checksum-crc32") warningConflictHeader: String, @@ -56,11 +57,13 @@ structure HeaderConflictsInput { validationMode: ValidationMode, } +@output structure HeaderConflictsOutput { @httpHeader("x-amz-checksum-CRC32") warningConflictHeader: String, } +@input structure HeadersConflictsInput { @httpPrefixHeaders("x-amz-checksum-") dangerConflictHeaders: StringMap, @@ -72,11 +75,13 @@ structure HeadersConflictsInput { validationMode: ValidationMode, } +@output structure HeadersConflictsOutput { @httpPrefixHeaders("x-amz-checksum-") dangerConflictHeaders: StringMap, } +@input structure NoConflictsInput { @httpHeader("x-safe-header") noConflictHeader: String, @@ -91,6 +96,7 @@ structure NoConflictsInput { validationMode: ValidationMode, } +@output structure NoConflictsOutput { @httpHeader("x-safe-header") noConflictHeader: String, diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-member-enums.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-member-enums.smithy index d7d99dc9dc3..2d47b3f72b4 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-member-enums.smithy +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-member-enums.smithy @@ -14,11 +14,13 @@ operation ValidEnums { } +@input structure ValidEnumsInput { requestAlgorithm: ChecksumAlgorithm, validationMode: ValidationMode, } +@output structure ValidEnumsOutput {} @httpChecksum( @@ -32,11 +34,13 @@ operation InvalidEnums { output: InvalidEnumsOutput, } +@input structure InvalidEnumsInput { requestAlgorithm: BadChecksumAlgorithm, validationMode: BadValidationMode, } +@output structure InvalidEnumsOutput {} @httpChecksum( @@ -50,11 +54,13 @@ operation NoEnums { output: NoEnumsOutput, } +@input structure NoEnumsInput { requestAlgorithm: String, validationMode: String, } +@output structure NoEnumsOutput {} @httpChecksum( @@ -68,8 +74,10 @@ operation NoMember { output: NoMemberOutput, } +@input structure NoMemberInput {} +@output structure NoMemberOutput {} @enum([ diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-trait.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-trait.errors index b9168aa36e4..fb7502a6eb9 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-trait.errors +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-trait.errors @@ -1,8 +1,5 @@ [SUPPRESSED] smithy.example#NoBehavior: This shape applies a trait that is unstable: aws.protocols#httpChecksum | UnstableTrait -[SUPPRESSED] smithy.example#NoInput: This shape applies a trait that is unstable: aws.protocols#httpChecksum | UnstableTrait [SUPPRESSED] smithy.example#NoModeForResponse: This shape applies a trait that is unstable: aws.protocols#httpChecksum | UnstableTrait [SUPPRESSED] smithy.example#NoOutputForResponse: This shape applies a trait that is unstable: aws.protocols#httpChecksum | UnstableTrait [ERROR] smithy.example#NoBehavior: The `httpChecksum` trait must define at least one of the `request` or `response` checksum behaviors. | HttpChecksumTrait -[ERROR] smithy.example#NoInput: Trait `aws.protocols#httpChecksum` cannot be applied to `smithy.example#NoInput`. This trait may only be applied to shapes that match the following selector: operation :test(-[input]->) | TraitTarget [ERROR] smithy.example#NoModeForResponse: The `httpChecksum` trait must model the `requestValidationModeMember` property to support response checksum behavior. | HttpChecksumTrait -[ERROR] smithy.example#NoOutputForResponse: The `httpChecksum` trait defines `response` checksum behavior but the operation does not have output. | HttpChecksumTrait diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-trait.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-trait.smithy index 053b3d5dec6..585baf15bfa 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-trait.smithy +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/http-checksum-trait.smithy @@ -6,15 +6,19 @@ use aws.protocols#httpChecksum @suppress(["UnstableTrait"]) operation NoBehavior { input: NoBehaviorInput, + output: Unit } +@input structure NoBehaviorInput {} @httpChecksum( requestChecksumRequired: true, ) -@suppress(["UnstableTrait"]) -operation NoInput {} +@suppress(["UnstableTrait", "OperationMissingInput"]) +operation NoInput { + output: Unit +} @httpChecksum( responseAlgorithms: ["CRC32C"] @@ -23,11 +27,12 @@ operation NoInput {} operation NoModeForResponse { input: NoModeForResponseInput, output: NoModeForResponseOutput, - } +@input structure NoModeForResponseInput {} +@output structure NoModeForResponseOutput {} @httpChecksum( @@ -38,9 +43,10 @@ structure NoModeForResponseOutput {} @suppress(["UnstableTrait"]) operation NoOutputForResponse { input: NoOutputForResponseInput, - + output: Unit } +@input structure NoOutputForResponseInput { requestAlgorithm: ChecksumAlgorithm, validationMode: ValidationMode, diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/aws-protocols-do-not-support-list-set-map-payloads.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/aws-protocols-do-not-support-list-set-map-payloads.errors index 265c5db3888..1809266f026 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/aws-protocols-do-not-support-list-set-map-payloads.errors +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/aws-protocols-do-not-support-list-set-map-payloads.errors @@ -1,3 +1,3 @@ -[ERROR] smithy.example#InvalidBindingInput$listBinding: AWS Protocols do not support applying the httpPayload trait to members that target sets, lists, or maps. | ProtocolHttpPayload -[ERROR] smithy.example#InvalidBindingOutput$mapBinding: AWS Protocols do not support applying the httpPayload trait to members that target sets, lists, or maps. | ProtocolHttpPayload +[ERROR] smithy.example#InvalidBindingOperationInput$listBinding: AWS Protocols do not support applying the httpPayload trait to members that target sets, lists, or maps. | ProtocolHttpPayload +[ERROR] smithy.example#InvalidBindingOperationOutput$mapBinding: AWS Protocols do not support applying the httpPayload trait to members that target sets, lists, or maps. | ProtocolHttpPayload [ERROR] smithy.example#InvalidBindingError$setBinding: AWS Protocols do not support applying the httpPayload trait to members that target sets, lists, or maps. | ProtocolHttpPayload diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/aws-protocols-do-not-support-list-set-map-payloads.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/aws-protocols-do-not-support-list-set-map-payloads.smithy index cabfe16a331..5f7ef68f6d7 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/aws-protocols-do-not-support-list-set-map-payloads.smithy +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/aws-protocols-do-not-support-list-set-map-payloads.smithy @@ -15,17 +15,19 @@ service InvalidExample { @http(method: "POST", uri: "/invalid-payload") operation InvalidBindingOperation { - input: InvalidBindingInput, - output: InvalidBindingOutput, + input: InvalidBindingOperationInput, + output: InvalidBindingOperationOutput, errors: [InvalidBindingError], } -structure InvalidBindingInput { +@input +structure InvalidBindingOperationInput { @httpPayload listBinding: StringList, } -structure InvalidBindingOutput { +@output +structure InvalidBindingOperationOutput { @httpPayload mapBinding: StringMap, } diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy index 3d1b24ad37f..fc89299f107 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy @@ -15,10 +15,15 @@ service InvalidExample { operation Operation1 { input: Operation1Input, + output: Operation1Output } +@input structure Operation1Input { foo: InlineDocument, } document InlineDocument + +@output +structure Operation1Output {} diff --git a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/TopologicalIndexTest.java b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/TopologicalIndexTest.java index 7135fde79d1..3da0bc94a44 100644 --- a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/TopologicalIndexTest.java +++ b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/TopologicalIndexTest.java @@ -19,7 +19,7 @@ import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.utils.FunctionalUtils; +import software.amazon.smithy.model.traits.UnitTypeTrait; public class TopologicalIndexTest { @@ -114,9 +114,10 @@ public void handlesMoreRecursion() { .unwrap(); TopologicalIndex index = TopologicalIndex.of(recursive); - // The topological index must capture all shapes in the index not in the prelude. + // The topological index must capture all shapes in the index not in the prelude, + // but include the Unit shape. Set nonPrelude = recursive.shapes() - .filter(FunctionalUtils.not(Prelude::isPreludeShape)) + .filter(shape -> shape.getId().equals(UnitTypeTrait.UNIT) || !Prelude.isPreludeShape(shape)) .collect(Collectors.toSet()); Set topologicalShapes = new HashSet<>(index.getOrderedShapes()); topologicalShapes.addAll(index.getRecursiveShapes()); @@ -128,6 +129,7 @@ public void handlesMoreRecursion() { .map(ShapeId::toString) .collect(Collectors.toList()); assertThat(orderedIds, contains( + "smithy.api#Unit", "smithy.example#MyString", "smithy.example#NonRecursive$foo", "smithy.example#NonRecursive", diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedOperationInputOutput.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedOperationInputOutput.java index 182df26da45..e6709f0b6cb 100644 --- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedOperationInputOutput.java +++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedOperationInputOutput.java @@ -15,79 +15,20 @@ package software.amazon.smithy.diff.evaluators; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import software.amazon.smithy.diff.ChangedShape; import software.amazon.smithy.diff.DiffEvaluator; import software.amazon.smithy.diff.Differences; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.shapes.MemberShape; -import software.amazon.smithy.model.shapes.OperationShape; -import software.amazon.smithy.model.shapes.Shape; -import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.validation.Severity; import software.amazon.smithy.model.validation.ValidationEvent; /** - * Meta-validator that emits a NOTE when a backward compatible operation - * input or output is added (AddedOperationInput and AddedOperationOutput), - * and an ERROR when a breaking operation input is added using the same - * event IDs. - * - *

TODO: Also check for the addition of streaming and event streams. + * This validator is now deprecated because operations always default + * to smithy.api#Unit when they have no input or output. */ +@Deprecated public final class AddedOperationInputOutput implements DiffEvaluator { @Override public List evaluate(Differences differences) { - return differences.changedShapes(OperationShape.class) - .flatMap(change -> checkOperation(differences.getNewModel(), change).stream()) - .collect(Collectors.toList()); - } - - private List checkOperation(Model newModel, ChangedShape change) { - List events = new ArrayList<>(2); - if (!change.getOldShape().getInput().isPresent() && change.getNewShape().getInput().isPresent()) { - validateChange("Input", newModel, change.getNewShape(), change.getNewShape().getInput().get()) - .ifPresent(events::add); - } - - if (!change.getOldShape().getOutput().isPresent() && change.getNewShape().getOutput().isPresent()) { - validateChange("Output", newModel, change.getNewShape(), change.getNewShape().getOutput().get()) - .ifPresent(events::add); - } - - return events; - } - - private Optional validateChange(String rel, Model model, Shape operation, ShapeId target) { - String eventId = "AddedOperation" + rel; - - return model.getShape(target).flatMap(Shape::asStructureShape).map(struct -> { - if (struct.getAllMembers().values().stream().noneMatch(MemberShape::isRequired)) { - // This is a backward compatible change. - return ValidationEvent.builder() - .id(eventId) - .severity(Severity.NOTE) - .shape(operation) - .message(String.format( - "%s shape `%s` was added to the `%s` operation", - rel, target, operation.getId())) - .build(); - } - - // This is a breaking change! - return ValidationEvent.builder() - .id(eventId) - .severity(Severity.ERROR) - .shape(operation) - .message(String.format( - "%s shape `%s` was added to the `%s` operation, and this structure contains " - + "one or more required members. %s can only be added to an operation if the targeted " - + "structure contains no required members.", - rel, target, operation.getId(), rel)) - .build(); - }); + return Collections.emptyList(); } } diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedOperationInput.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedOperationInput.java index a1a2c33a32e..4a9a1ca51fc 100644 --- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedOperationInput.java +++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedOperationInput.java @@ -29,14 +29,12 @@ public final class ChangedOperationInput extends AbstractDiffEvaluator { @Override public List evaluate(Differences differences) { return differences.changedShapes(OperationShape.class) - .filter(change -> change.getOldShape().getInput().isPresent() - && change.getNewShape().getInput().isPresent() - && !change.getOldShape().getInput().equals(change.getNewShape().getInput())) + .filter(change -> !change.getOldShape().getInputShape().equals(change.getNewShape().getInputShape())) .map(change -> error(change.getNewShape(), String.format( "Input shape of `%s` changed from `%s` to `%s`", change.getShapeId(), - change.getOldShape().getInput().get(), - change.getNewShape().getInput().get()))) + change.getOldShape().getInputShape(), + change.getNewShape().getOutputShape()))) .collect(Collectors.toList()); } } diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedOperationOutput.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedOperationOutput.java index d20a8159a4a..a9dcb408c09 100644 --- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedOperationOutput.java +++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedOperationOutput.java @@ -29,14 +29,12 @@ public final class ChangedOperationOutput extends AbstractDiffEvaluator { @Override public List evaluate(Differences differences) { return differences.changedShapes(OperationShape.class) - .filter(change -> change.getOldShape().getOutput().isPresent() - && change.getNewShape().getOutput().isPresent() - && !change.getOldShape().getOutput().equals(change.getNewShape().getOutput())) + .filter(change -> !change.getOldShape().getOutputShape().equals(change.getNewShape().getOutputShape())) .map(change -> error(change.getNewShape(), String.format( "Output shape of `%s` changed from `%s` to `%s`", change.getShapeId(), - change.getOldShape().getOutput().get(), - change.getNewShape().getOutput().get()))) + change.getOldShape().getOutputShape(), + change.getNewShape().getOutputShape()))) .collect(Collectors.toList()); } } diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedOperationInput.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedOperationInput.java index a8382b5be44..e52f43804a3 100644 --- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedOperationInput.java +++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedOperationInput.java @@ -15,25 +15,19 @@ package software.amazon.smithy.diff.evaluators; +import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import software.amazon.smithy.diff.Differences; -import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.validation.ValidationEvent; /** - * Emits an ERROR when the input shape is removed from an operation. + * This validator is now deprecated because operations always default + * to smithy.api#Unit when they have no input or output. */ +@Deprecated public final class RemovedOperationInput extends AbstractDiffEvaluator { @Override public List evaluate(Differences differences) { - return differences.changedShapes(OperationShape.class) - .filter(change -> change.getOldShape().getInput().isPresent() - && !change.getNewShape().getInput().isPresent()) - .map(change -> error(change.getNewShape(), String.format( - "Input shape `%s` was removed from the `%s` operation", - change.getOldShape().getInput().get(), - change.getShapeId()))) - .collect(Collectors.toList()); + return Collections.emptyList(); } } diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedOperationOutput.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedOperationOutput.java index 84334101e25..739b9be21ee 100644 --- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedOperationOutput.java +++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedOperationOutput.java @@ -15,25 +15,19 @@ package software.amazon.smithy.diff.evaluators; +import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import software.amazon.smithy.diff.Differences; -import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.validation.ValidationEvent; /** - * Emits an ERROR when the output shape is removed from an operation. + * This validator is now deprecated because operations always default + * to smithy.api#Unit when they have no input or output. */ +@Deprecated public final class RemovedOperationOutput extends AbstractDiffEvaluator { @Override public List evaluate(Differences differences) { - return differences.changedShapes(OperationShape.class) - .filter(change -> change.getOldShape().getOutput().isPresent() - && !change.getNewShape().getOutput().isPresent()) - .map(change -> error(change.getNewShape(), String.format( - "Output shape, `%s`, was removed from the `%s` operation", - change.getOldShape().getOutput().get(), - change.getShapeId()))) - .collect(Collectors.toList()); + return Collections.emptyList(); } } diff --git a/smithy-diff/src/main/resources/META-INF/services/software.amazon.smithy.diff.DiffEvaluator b/smithy-diff/src/main/resources/META-INF/services/software.amazon.smithy.diff.DiffEvaluator index ac694654b0f..35673bbbccc 100644 --- a/smithy-diff/src/main/resources/META-INF/services/software.amazon.smithy.diff.DiffEvaluator +++ b/smithy-diff/src/main/resources/META-INF/services/software.amazon.smithy.diff.DiffEvaluator @@ -1,7 +1,6 @@ software.amazon.smithy.diff.evaluators.AddedEntityBinding software.amazon.smithy.diff.evaluators.AddedMetadata software.amazon.smithy.diff.evaluators.AddedOperationError -software.amazon.smithy.diff.evaluators.AddedOperationInputOutput software.amazon.smithy.diff.evaluators.AddedServiceError software.amazon.smithy.diff.evaluators.AddedShape software.amazon.smithy.diff.evaluators.AddedTraitDefinition @@ -20,8 +19,6 @@ software.amazon.smithy.diff.evaluators.RemovedAuthenticationScheme software.amazon.smithy.diff.evaluators.RemovedEntityBinding software.amazon.smithy.diff.evaluators.RemovedMetadata software.amazon.smithy.diff.evaluators.RemovedOperationError -software.amazon.smithy.diff.evaluators.RemovedOperationInput -software.amazon.smithy.diff.evaluators.RemovedOperationOutput software.amazon.smithy.diff.evaluators.RemovedServiceError software.amazon.smithy.diff.evaluators.RemovedShape software.amazon.smithy.diff.evaluators.RemovedTraitDefinition diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedOperationInputOutputTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedOperationInputOutputTest.java deleted file mode 100644 index d8b90c62749..00000000000 --- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedOperationInputOutputTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.diff.evaluators; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -import java.util.List; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.diff.ModelDiff; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.shapes.MemberShape; -import software.amazon.smithy.model.shapes.OperationShape; -import software.amazon.smithy.model.shapes.StringShape; -import software.amazon.smithy.model.shapes.StructureShape; -import software.amazon.smithy.model.traits.RequiredTrait; -import software.amazon.smithy.model.validation.Severity; -import software.amazon.smithy.model.validation.ValidationEvent; - -public class AddedOperationInputOutputTest { - @Test - public void detectsCompatibleInput() { - StructureShape a = StructureShape.builder().id("foo.baz#A").build(); - OperationShape o1 = OperationShape.builder().id("foo.baz#Bar").build(); - OperationShape o2 = OperationShape.builder().id("foo.baz#Bar").input(a).build(); - Model modelA = Model.assembler().addShapes(o1, a).assemble().unwrap(); - Model modelB = Model.assembler().addShapes(o2, a).assemble().unwrap(); - List events = ModelDiff.compare(modelA, modelB); - - assertThat(TestHelper.findEvents(events, "AddedOperationInput").size(), equalTo(1)); - assertThat(TestHelper.findEvents(events, Severity.NOTE).size(), equalTo(1)); - } - - @Test - public void detectsCompatibleOutput() { - StructureShape a = StructureShape.builder().id("foo.baz#A").build(); - OperationShape o1 = OperationShape.builder().id("foo.baz#Bar").build(); - OperationShape o2 = OperationShape.builder().id("foo.baz#Bar").output(a).build(); - Model modelA = Model.assembler().addShapes(o1, a).assemble().unwrap(); - Model modelB = Model.assembler().addShapes(o2, a).assemble().unwrap(); - List events = ModelDiff.compare(modelA, modelB); - - assertThat(TestHelper.findEvents(events, "AddedOperationOutput").size(), equalTo(1)); - assertThat(TestHelper.findEvents(events, Severity.NOTE).size(), equalTo(1)); - } - - @Test - public void detectsIncompatibleInput() { - StringShape string = StringShape.builder().id("foo.baz#String").build(); - MemberShape member = MemberShape.builder() - .id("foo.baz#A$m") - .target(string) - .addTrait(new RequiredTrait()) - .build(); - StructureShape struct = StructureShape.builder().id("foo.baz#A").addMember(member).build(); - OperationShape o1 = OperationShape.builder().id("foo.baz#Bar").build(); - OperationShape o2 = OperationShape.builder().id("foo.baz#Bar").input(struct).build(); - Model modelA = Model.assembler().addShapes(o1, struct, member, string).assemble().unwrap(); - Model modelB = Model.assembler().addShapes(o2, struct, member, string).assemble().unwrap(); - List events = ModelDiff.compare(modelA, modelB); - - assertThat(TestHelper.findEvents(events, "AddedOperationInput").size(), equalTo(1)); - assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(1)); - } - - @Test - public void detectsIncompatibleOutput() { - StringShape string = StringShape.builder().id("foo.baz#String").build(); - MemberShape member = MemberShape.builder() - .id("foo.baz#A$m") - .target(string) - .addTrait(new RequiredTrait()) - .build(); - StructureShape struct = StructureShape.builder().id("foo.baz#A").addMember(member).build(); - OperationShape o1 = OperationShape.builder().id("foo.baz#Bar").build(); - OperationShape o2 = OperationShape.builder().id("foo.baz#Bar").output(struct).build(); - Model modelA = Model.assembler().addShapes(o1, struct, member, string).assemble().unwrap(); - Model modelB = Model.assembler().addShapes(o2, struct, member, string).assemble().unwrap(); - List events = ModelDiff.compare(modelA, modelB); - - assertThat(TestHelper.findEvents(events, "AddedOperationOutput").size(), equalTo(1)); - assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(1)); - } -} diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationInputTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationInputTest.java deleted file mode 100644 index 40d49181e5c..00000000000 --- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationInputTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.diff.evaluators; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -import java.util.List; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.diff.ModelDiff; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.shapes.OperationShape; -import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.shapes.StructureShape; -import software.amazon.smithy.model.validation.Severity; -import software.amazon.smithy.model.validation.ValidationEvent; - -public class RemovedOperationInputTest { - @Test - public void detectWhenOperationInputIsRemoved() { - OperationShape a = OperationShape.builder().id("foo.baz#Bar").input(ShapeId.from("foo.baz#Input")).build(); - OperationShape b = OperationShape.builder().id("foo.baz#Bar").build(); - StructureShape input = StructureShape.builder().id("foo.baz#Input").build(); - Model modelA = Model.assembler().addShapes(a, input).assemble().unwrap(); - Model modelB = Model.assembler().addShapes(b, input).assemble().unwrap(); - List events = ModelDiff.compare(modelA, modelB); - - assertThat(TestHelper.findEvents(events, "RemovedOperationInput").size(), equalTo(1)); - assertThat(TestHelper.findEvents(events, a.getId()).size(), equalTo(1)); - assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(1)); - } -} diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationOutputTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationOutputTest.java deleted file mode 100644 index f479fed9faa..00000000000 --- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationOutputTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.diff.evaluators; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -import java.util.List; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.diff.ModelDiff; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.shapes.OperationShape; -import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.shapes.StructureShape; -import software.amazon.smithy.model.validation.Severity; -import software.amazon.smithy.model.validation.ValidationEvent; - -public class RemovedOperationOutputTest { - @Test - public void detectWhenOperationOutputIsRemoved() { - OperationShape a = OperationShape.builder().id("foo.baz#Bar").output(ShapeId.from("foo.baz#Output")).build(); - OperationShape b = OperationShape.builder().id("foo.baz#Bar").build(); - StructureShape output = StructureShape.builder().id("foo.baz#Output").build(); - Model modelA = Model.assembler().addShapes(a, output).assemble().unwrap(); - Model modelB = Model.assembler().addShapes(b, output).assemble().unwrap(); - List events = ModelDiff.compare(modelA, modelB); - - assertThat(TestHelper.findEvents(events, "RemovedOperationOutput").size(), equalTo(1)); - assertThat(TestHelper.findEvents(events, a.getId()).size(), equalTo(1)); - assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(1)); - } -} diff --git a/smithy-linters/src/main/java/software/amazon/smithy/linters/InputOutputStructureReuseValidator.java b/smithy-linters/src/main/java/software/amazon/smithy/linters/InputOutputStructureReuseValidator.java index 4ec8a77a87f..6bddf501e3c 100644 --- a/smithy-linters/src/main/java/software/amazon/smithy/linters/InputOutputStructureReuseValidator.java +++ b/smithy-linters/src/main/java/software/amazon/smithy/linters/InputOutputStructureReuseValidator.java @@ -22,7 +22,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -33,12 +32,16 @@ import software.amazon.smithy.model.validation.Severity; import software.amazon.smithy.model.validation.ValidationEvent; import software.amazon.smithy.model.validation.ValidatorService; -import software.amazon.smithy.utils.Pair; /** * Checks if a structure is used as both input and output and if the same * input or output structures are used across multiple operations. + * + * @deprecated This validator is superseded by the input and output traits + * now built-in to Smithy. This validator remains for backward + * compatibility, but is intentionally no longer documented. */ +@Deprecated public final class InputOutputStructureReuseValidator extends AbstractValidator { public static final class Provider extends ValidatorService.Provider { @@ -50,8 +53,8 @@ public Provider() { @Override public List validate(Model model) { List events = new ArrayList<>(); - Map> inputs = createStructureToOperation(model, OperationShape::getInput); - Map> outputs = createStructureToOperation(model, OperationShape::getOutput); + Map> inputs = createStructureToOperation(model, OperationShape::getInputShape); + Map> outputs = createStructureToOperation(model, OperationShape::getOutputShape); // Look for structures used as both input and output. Set both = new HashSet<>(inputs.keySet()); @@ -85,15 +88,14 @@ private List emitShared( */ private static Map> createStructureToOperation( Model model, - Function> f + Function f ) { - return model.shapes(OperationShape.class) - .flatMap(shape -> Pair.flatMapStream(shape, f)) - .collect(Collectors.groupingBy( - Pair::getRight, - HashMap::new, - Collectors.mapping(pair -> pair.getLeft().getId(), Collectors.toSet()) - )); + Map> result = new HashMap<>(); + for (OperationShape operation : model.getOperationShapes()) { + ShapeId struct = f.apply(operation); + result.computeIfAbsent(struct, id -> new HashSet<>()).add(operation.getId()); + } + return result; } private ValidationEvent emitWhenBothInputAndOutput( diff --git a/smithy-linters/src/main/java/software/amazon/smithy/linters/MissingPaginatedTraitValidator.java b/smithy-linters/src/main/java/software/amazon/smithy/linters/MissingPaginatedTraitValidator.java index dac9f3c2b5a..d16d4236e08 100644 --- a/smithy-linters/src/main/java/software/amazon/smithy/linters/MissingPaginatedTraitValidator.java +++ b/smithy-linters/src/main/java/software/amazon/smithy/linters/MissingPaginatedTraitValidator.java @@ -183,27 +183,21 @@ private Stream validateShape( + "`paginated` trait. %s", verb, DISCLAIMER))); } - if (operationIndex.getInput(operation.getId()).isPresent()) { - StructureShape input = operationIndex.getInput(operation.getId()).get(); - Optional member = findMember( - input.getAllMembers().keySet(), config.getInputMembersRequirePagination()); - if (member.isPresent()) { - return Stream.of(danger(operation, format( - "This operation contains an input member, `%s`, that requires that the operation is " - + "marked with the `paginated` trait. %s", member.get(), DISCLAIMER))); - } - } - - if (operationIndex.getOutput(operation.getId()).isPresent()) { - StructureShape output = operationIndex.getOutput(operation.getId()).get(); - return findMember(output.getAllMembers().keySet(), config.getOutputMembersRequirePagination()) - .map(member -> Stream.of(danger(operation, format( - "This operation contains an output member, `%s`, that requires that the " - + "operation is marked with the `paginated` trait. %s", member, DISCLAIMER)))) - .orElseGet(() -> suggestPagination(verb, operation, output, model)); + StructureShape input = operationIndex.expectInputShape(operation.getId()); + Optional member = findMember( + input.getAllMembers().keySet(), config.getInputMembersRequirePagination()); + if (member.isPresent()) { + return Stream.of(danger(operation, format( + "This operation contains an input member, `%s`, that requires that the operation is " + + "marked with the `paginated` trait. %s", member.get(), DISCLAIMER))); } - return Stream.empty(); + StructureShape output = operationIndex.expectOutputShape(operation.getId()); + return findMember(output.getAllMembers().keySet(), config.getOutputMembersRequirePagination()) + .map(outputMember -> Stream.of(danger(operation, format( + "This operation contains an output member, `%s`, that requires that the " + + "operation is marked with the `paginated` trait. %s", outputMember, DISCLAIMER)))) + .orElseGet(() -> suggestPagination(verb, operation, output, model)); } private Stream suggestPagination( diff --git a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/contains-reserved-words-validator.json b/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/contains-reserved-words-validator.json index 3c1348d7f6f..2a469b75b54 100644 --- a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/contains-reserved-words-validator.json +++ b/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/contains-reserved-words-validator.json @@ -1,29 +1,6 @@ { "smithy": "1.0", "shapes": { - "ns.foo#_foo": { - "type": "structure", - "members": { - "bar": { - "target": "ns.foo#String" - }, - "long": { - "target": "ns.foo#Long" - }, - "bool": { - "target": "ns.foo#Boolean" - }, - "list": { - "target": "ns.foo#List" - }, - "timestamp": { - "target": "ns.foo#UtcTimestamp" - }, - "blob": { - "target": "ns.foo#Blob" - } - } - }, "ns.foo#String": { "type": "string" }, @@ -119,6 +96,9 @@ "fooShape": { "target": "ns.foo#FooShape" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#OperationAOutput": { @@ -127,6 +107,9 @@ "b": { "target": "ns.foo#Map" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#OperationErrorA": { @@ -156,6 +139,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#_OperationBaz": { @@ -164,6 +150,32 @@ "target": "ns.foo#_foo" } }, + "ns.foo#_foo": { + "type": "structure", + "members": { + "bar": { + "target": "ns.foo#String" + }, + "long": { + "target": "ns.foo#Long" + }, + "bool": { + "target": "ns.foo#Boolean" + }, + "list": { + "target": "ns.foo#List" + }, + "timestamp": { + "target": "ns.foo#UtcTimestamp" + }, + "blob": { + "target": "ns.foo#Blob" + } + }, + "traits": { + "smithy.api#input": {} + } + }, "ns.foo#UtcTimestamp": { "type": "timestamp" }, @@ -189,6 +201,11 @@ } }, "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "ns.foo"}, + {"id": "OperationMissingOutput", "namespace": "ns.foo"}, + {"id": "OperationInputOutputName", "namespace": "ns.foo"} + ], "validators": [ { "id": "FooReservedWords", diff --git a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/input-output-structure-reuse.json b/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/input-output-structure-reuse.json index 4b06cf5395e..8cebe2e7946 100644 --- a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/input-output-structure-reuse.json +++ b/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/input-output-structure-reuse.json @@ -105,6 +105,12 @@ } }, "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "ns.foo"}, + {"id": "OperationMissingOutput", "namespace": "ns.foo"}, + {"id": "OperationMissingInputTrait", "namespace": "ns.foo"}, + {"id": "OperationMissingOutputTrait", "namespace": "ns.foo"} + ], "validators": [ { "name": "InputOutputStructureReuse" diff --git a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-documentation-selector-test.errors b/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-documentation-selector-test.errors deleted file mode 100644 index dbc9cc99363..00000000000 --- a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-documentation-selector-test.errors +++ /dev/null @@ -1,5 +0,0 @@ -[DANGER] ns.foo#Map: This shape should have documentation | MissingDocumentation -[DANGER] ns.foo#MyService: This shape should have documentation | MissingDocumentation -[DANGER] ns.foo#SomeList: This shape should have documentation | MissingDocumentation -[DANGER] ns.foo#Structure$baz: This shape should have documentation | MissingDocumentation -[DANGER] ns.foo#Structure: This shape should have documentation | MissingDocumentation diff --git a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-documentation-selector-test.json b/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-documentation-selector-test.json deleted file mode 100644 index 2562906d5b2..00000000000 --- a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-documentation-selector-test.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "smithy": "1.0", - "shapes": { - "ns.foo#MyService": { - "type": "service", - "version": "2017-01-17", - "operations": [ - { - "target": "ns.foo#Operation" - } - ] - }, - "ns.foo#Operation": { - "type": "operation", - "input": { - "target": "ns.foo#Structure" - }, - "traits": { - "smithy.api#documentation": "operation documentation" - } - }, - "ns.foo#SomeList": { - "type": "list", - "member": { - "target": "ns.foo#String" - } - }, - "ns.foo#Map": { - "type": "map", - "key": { - "target": "ns.foo#String" - }, - "value": { - "target": "ns.foo#String" - } - }, - "ns.foo#Structure": { - "type": "structure", - "members": { - "foo": { - "target": "ns.foo#String", - "traits": { - "smithy.api#documentation": "docs" - } - }, - "baz": { - "target": "ns.foo#String" - }, - "bar": { - "target": "ns.foo#DocString" - }, - "someList": { - "target": "ns.foo#SomeList", - "traits": { - "smithy.api#documentation": "list documentation" - } - }, - "map": { - "target": "ns.foo#Map", - "traits": { - "smithy.api#documentation": "map documentation" - } - } - } - }, - "ns.foo#String": { - "type": "string" - }, - "ns.foo#DocString": { - "type": "string", - "traits": { - "smithy.api#documentation": "docs" - } - } - }, - "metadata": { - "validators": [ - { - "name": "EmitEachSelector", - "id": "MissingDocumentation", - "message": "This shape should have documentation", - "configuration": { - "selector": " :not([trait|documentation])\n :not(simpleType)\n :not(member:test(< list, < map))\n :not(:test(member > [trait|documentation]))" - } - } - ] - } -} diff --git a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-paginated-trait-validator.json b/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-paginated-trait-validator.json index 9bd63c0c07b..50a42517a32 100644 --- a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-paginated-trait-validator.json +++ b/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/missing-paginated-trait-validator.json @@ -15,12 +15,29 @@ }, "ns.foo#ListFoos": { "type": "operation", + "input": {"target": "ns.foo#ListFoosInput"}, + "output": {"target": "ns.foo#ListFoosOutput"}, "traits": { "smithy.api#readonly": {} } }, + "ns.foo#ListFoosInput": { + "type": "structure", + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#ListFoosOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} + } + }, "ns.foo#IgnoreMe": { "type": "operation", + "input": { + "target": "ns.foo#IgnoreMeInput" + }, "output": { "target": "ns.foo#IgnoreMeOutput" }, @@ -28,8 +45,17 @@ "smithy.api#readonly": {} } }, + "ns.foo#IgnoreMeInput": { + "type": "structure", + "traits": { + "smithy.api#input": {} + } + }, "ns.foo#IgnoreMeOutput": { - "type": "structure" + "type": "structure", + "traits": { + "smithy.api#output": {} + } }, "ns.foo#IgnoreMeToo": { "type": "operation", @@ -58,6 +84,9 @@ "maxResults": { "target": "ns.foo#Integer" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#IgnoreMeTooOutput": { @@ -72,6 +101,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#A": { @@ -89,6 +121,9 @@ "nextToken": { "target": "ns.foo#String" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#B": { @@ -106,6 +141,9 @@ "marker": { "target": "ns.foo#String" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#GetFoos": { @@ -126,6 +164,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#GetBars": { @@ -139,7 +180,10 @@ }, "ns.foo#GetBarsOutput": { "type": "structure", - "members": {} + "members": {}, + "traits": { + "smithy.api#output": {} + } }, "ns.foo#SchwiftyFoos": { "type": "operation", @@ -165,6 +209,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#SomeOperation": { @@ -182,6 +229,9 @@ "fizz": { "target": "ns.foo#String" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#SomeOtherOperation": { @@ -199,10 +249,17 @@ "buzz": { "target": "ns.foo#String" } + }, + "traits": { + "smithy.api#output": {} } } }, "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "ns.foo"}, + {"id": "OperationMissingOutput", "namespace": "ns.foo"} + ], "validators": [ { "name": "MissingPaginatedTrait" diff --git a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/standard-operation-verb-validator-test.json b/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/standard-operation-verb-validator-test.json index ea4fdf167a1..af62a4653b2 100644 --- a/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/standard-operation-verb-validator-test.json +++ b/smithy-linters/src/test/resources/software/amazon/smithy/linters/errorfiles/standard-operation-verb-validator-test.json @@ -42,6 +42,10 @@ } }, "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "ns.foo"}, + {"id": "OperationMissingOutput", "namespace": "ns.foo"} + ], "validators": [ { "name": "StandardOperationVerb", diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamIndex.java index 8f1b2fb6fd1..81b34cd9515 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamIndex.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamIndex.java @@ -46,12 +46,8 @@ public EventStreamIndex(Model model) { OperationIndex operationIndex = OperationIndex.of(model); for (OperationShape operation : model.getOperationShapes()) { - operationIndex.getInput(operation).ifPresent(input -> { - computeEvents(model, operation, input, inputInfo); - }); - operationIndex.getOutput(operation).ifPresent(output -> { - computeEvents(model, operation, output, outputInfo); - }); + computeEvents(model, operation, operationIndex.expectInputShape(operation), inputInfo); + computeEvents(model, operation, operationIndex.expectOutputShape(operation), outputInfo); } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/HttpBindingIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/HttpBindingIndex.java index 94223be6f94..14fe51b68f7 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/HttpBindingIndex.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/HttpBindingIndex.java @@ -387,15 +387,11 @@ private String determineContentType( } private List computeRequestBindings(OperationIndex opIndex, OperationShape shape) { - return opIndex.getInput(shape.getId()) - .map(input -> createStructureBindings(input, true)) - .orElseGet(Collections::emptyList); + return createStructureBindings(opIndex.expectInputShape(shape.getId()), true); } private List computeResponseBindings(OperationIndex opIndex, OperationShape shape) { - return opIndex.getOutput(shape.getId()) - .map(output -> createStructureBindings(output, false)) - .orElseGet(Collections::emptyList); + return createStructureBindings(opIndex.expectOutputShape(shape.getId()), false); } private List createStructureBindings(StructureShape struct, boolean isRequest) { diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/IdentifierBindingIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/IdentifierBindingIndex.java index f60d5c4ac27..703922bc6b2 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/IdentifierBindingIndex.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/IdentifierBindingIndex.java @@ -24,7 +24,6 @@ import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.ResourceShape; -import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.ToShapeId; @@ -100,8 +99,7 @@ private void processResource(ResourceShape resource, OperationIndex operationInd bindingTypes.put(resource.getId(), new HashMap<>()); resource.getAllOperations().forEach(operationId -> { // Ignore broken models in this index. - Map computedBindings = model.getShape(operationId).flatMap(Shape::asOperationShape) - .flatMap(operationIndex::getInput) + Map computedBindings = operationIndex.getInputShape(operationId) .map(inputShape -> computeBindings(resource, inputShape)) .orElseGet(HashMap::new); bindings.get(resource.getId()).put(operationId, computedBindings); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/OperationIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/OperationIndex.java index b95cc3f31d9..e3fecea2e37 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/OperationIndex.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/OperationIndex.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import java.util.Optional; import java.util.Set; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.ExpectationNotMetException; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; @@ -31,6 +33,9 @@ import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.ToShapeId; +import software.amazon.smithy.model.traits.InputTrait; +import software.amazon.smithy.model.traits.OutputTrait; +import software.amazon.smithy.model.traits.UnitTypeTrait; import software.amazon.smithy.utils.ListUtils; /** @@ -38,7 +43,8 @@ * structures. * *

This index performs no validation that the input, output, and - * errors actually reference valid structures. + * errors actually reference valid structures. Such operation inputs, + * outputs, and errors may be discarded as if they do not exist. */ public final class OperationIndex implements KnowledgeIndex { private final Map inputs = new HashMap<>(); @@ -47,12 +53,8 @@ public final class OperationIndex implements KnowledgeIndex { public OperationIndex(Model model) { for (OperationShape operation : model.getOperationShapes()) { - operation.getInput() - .flatMap(id -> getStructure(model, id)) - .ifPresent(shape -> inputs.put(operation.getId(), shape)); - operation.getOutput() - .flatMap(id -> getStructure(model, id)) - .ifPresent(shape -> outputs.put(operation.getId(), shape)); + getStructure(model, operation.getInputShape()).ifPresent(shape -> inputs.put(operation.getId(), shape)); + getStructure(model, operation.getOutputShape()).ifPresent(shape -> outputs.put(operation.getId(), shape)); addErrorsFromShape(model, operation.getId(), operation.getErrors()); } @@ -74,15 +76,55 @@ public static OperationIndex of(Model model) { } /** - * Gets the optional input structure of an operation. + * Gets the optional input structure of an operation, and returns an + * empty optional if the input targets {@code smithy.api#Unit}. + * + *

This method is only here for backward compatibility. Instead, + * use {@link #getInputShape(ToShapeId)} and + * {@link #expectInputShape(ToShapeId)}. * * @param operation Operation to get the input structure of. * @return Returns the optional operation input structure. + * @deprecated Use {@link #getInputShape(ToShapeId)} instead. */ + @Deprecated public Optional getInput(ToShapeId operation) { + return getInputShape(operation).filter(shape -> !shape.getId().equals(UnitTypeTrait.UNIT)); + } + + /** + * Gets the optional input structure of an operation. + * + *

Operations in the model always have input. This operation will + * only return an empty optional if the given operation shape cannot + * be found in the model or if it is not an operation shape. + * + * @param operation Operation to get the input structure of. + * @return Returns the optional operation input structure. + */ + public Optional getInputShape(ToShapeId operation) { return Optional.ofNullable(inputs.get(operation.toShapeId())); } + /** + * Gets the input shape of an operation, and returns Smithy's Unit type + * trait if the operation has no meaningful input. + * + *

In general, this method should be used instead of + * {@link #getInputShape(ToShapeId)} when getting the input of operations + * that are known to exist. + * + * @param operation Operation to get the input of. + * @return Returns the input shape of the operation. + * @throws ExpectationNotMetException if the operation shape cannot be found. + */ + public StructureShape expectInputShape(ToShapeId operation) { + return getInputShape(operation).orElseThrow(() -> new ExpectationNotMetException( + "Cannot get the input of `" + operation.toShapeId() + "` because " + + "it is not an operation shape in the model.", + SourceLocation.NONE)); + } + /** * Gets the input members of an operation as a map of member names * to {@link MemberShape}. @@ -94,19 +136,23 @@ public Optional getInput(ToShapeId operation) { * @return Returns the map of members, or an empty map. */ public Map getInputMembers(ToShapeId operation) { - return getInput(operation) + return getInputShape(operation) .map(input -> input.getAllMembers()) .orElse(Collections.emptyMap()); } /** * Returns true if the given structure is used as input by any - * operation in the model. + * operation in the model or is marked with the input trait. * * @param structureId Structure to check. * @return Returns true if the structure is used as input. */ public boolean isInputStructure(ToShapeId structureId) { + if (structureId instanceof Shape && ((Shape) structureId).hasTrait(InputTrait.class)) { + return true; + } + ShapeId id = structureId.toShapeId(); for (StructureShape shape : inputs.values()) { @@ -119,15 +165,55 @@ public boolean isInputStructure(ToShapeId structureId) { } /** - * Gets the optional output structure of an operation. + * Gets the optional output structure of an operation, and returns an + * empty optional if the output targets {@code smithy.api#Unit}. + * + *

This method is only here for backward compatibility. Instead, + * use {@link #getOutputShape(ToShapeId)} and + * {@link #expectOutputShape(ToShapeId)}. * * @param operation Operation to get the output structure of. * @return Returns the optional operation output structure. + * @deprecated Use {@link #getOutputShape(ToShapeId)} instead. */ + @Deprecated public Optional getOutput(ToShapeId operation) { + return getOutputShape(operation).filter(shape -> !shape.getId().equals(UnitTypeTrait.UNIT)); + } + + /** + * Gets the optional output structure of an operation. + * + *

Operations in the model always have output. This operation will + * only return an empty optional if the given operation shape cannot + * be found in the model or if it is not an operation shape. + * + * @param operation Operation to get the output structure of. + * @return Returns the optional operation output structure. + */ + public Optional getOutputShape(ToShapeId operation) { return Optional.ofNullable(outputs.get(operation.toShapeId())); } + /** + * Gets the output shape of an operation, and returns Smithy's unit type + * trait if the operation has no meaningful output. + * + *

In general, this method should be used instead of + * {@link #getOutputShape(ToShapeId)} when getting the output of operations + * that are known to exist. + * + * @param operation Operation to get the output of. + * @return Returns the output shape of the operation. + * @throws ExpectationNotMetException if the operation shape cannot be found. + */ + public StructureShape expectOutputShape(ToShapeId operation) { + return getOutputShape(operation).orElseThrow(() -> new ExpectationNotMetException( + "Cannot get the output of `" + operation.toShapeId() + "` because " + + "it is not an operation shape in the model.", + SourceLocation.NONE)); + } + /** * Gets the output members of an operation as a map of member names * to {@link MemberShape}. @@ -139,19 +225,23 @@ public Optional getOutput(ToShapeId operation) { * @return Returns the map of members, or an empty map. */ public Map getOutputMembers(ToShapeId operation) { - return getOutput(operation) + return getOutputShape(operation) .map(output -> output.getAllMembers()) .orElse(Collections.emptyMap()); } /** * Returns true if the given structure is used as output by any - * operation in the model. + * operation in the model or is marked with the output trait. * * @param structureId Structure to check. * @return Returns true if the structure is used as output. */ public boolean isOutputStructure(ToShapeId structureId) { + if (structureId instanceof Shape && ((Shape) structureId).hasTrait(OutputTrait.class)) { + return true; + } + ShapeId id = structureId.toShapeId(); for (StructureShape shape : outputs.values()) { diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PaginatedIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PaginatedIndex.java index 4aab7e64fbd..f5e174c194e 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PaginatedIndex.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PaginatedIndex.java @@ -75,12 +75,8 @@ private Optional create( OperationShape operation, PaginatedTrait trait ) { - StructureShape input = opIndex.getInput(operation.getId()).orElse(null); - StructureShape output = opIndex.getOutput(operation.getId()).orElse(null); - - if (input == null || output == null) { - return Optional.empty(); - } + StructureShape input = opIndex.expectInputShape(operation.getId()); + StructureShape output = opIndex.expectOutputShape(operation.getId()); MemberShape inputToken = trait.getInputToken().flatMap(input::getMember).orElse(null); List outputTokenPath = trait.getOutputToken() diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/AstModelLoader.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/AstModelLoader.java index ed49871feff..6d0e0ea0769 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/AstModelLoader.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/AstModelLoader.java @@ -247,6 +247,14 @@ private void loadOperation(ShapeId id, ObjectNode node, FullyResolvedModelFile m .source(node.getSourceLocation()) .addErrors(loadOptionalTargetList(modelFile, id, node, ERRORS)); + if (!node.getMember("input").isPresent()) { + modelFile.events().add(LoaderUtils.createOperationMissingInput(id, node.getSourceLocation())); + } + + if (!node.getMember("output").isPresent()) { + modelFile.events().add(LoaderUtils.createOperationMissingOutput(id, node.getSourceLocation())); + } + loadOptionalTarget(modelFile, id, node, "input").ifPresent(builder::input); loadOptionalTarget(modelFile, id, node, "output").ifPresent(builder::output); modelFile.onShape(builder); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java index ca5b6aec7ec..55970ff7a49 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java @@ -549,6 +549,15 @@ private void parseOperationStatement(ShapeId id, SourceLocation location) { ObjectNode node = IdlNodeParser.parseObjectNode(this, id.toString()); LoaderUtils.checkForAdditionalProperties(node, id, OPERATION_PROPERTY_NAMES, modelFile.events()); modelFile.onShape(builder); + + if (!node.getMember("input").isPresent()) { + modelFile.events().add(LoaderUtils.createOperationMissingInput(id, location)); + } + + if (!node.getMember("output").isPresent()) { + modelFile.events().add(LoaderUtils.createOperationMissingOutput(id, location)); + } + optionalId(node, "input", builder::input); optionalId(node, "output", builder::output); optionalIdList(node, ERRORS_KEYS, builder::addError); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/LoaderUtils.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/LoaderUtils.java index 83ae57ef033..41b1c10cfad 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/LoaderUtils.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/LoaderUtils.java @@ -110,4 +110,42 @@ static boolean containsErrorEvents(List events) { } return false; } + + /** + * Helper method to create a validation event when input is not explicitly defined. + * + * @param id Shape ID of the operation. + * @param location Where the operation is defined. + * @return Returns the event. + */ + static ValidationEvent createOperationMissingInput(ShapeId id, SourceLocation location) { + return ValidationEvent.builder() + .id("OperationMissingInput") + .severity(Severity.WARNING) + .shapeId(id) + .sourceLocation(location) + .message("This operation does not define input, so it defaults to smithy.api#Unit. Defining input " + + "for an operation, even if you don't need it today, future-proofs the operation by " + + "allowing input members to be added later.") + .build(); + } + + /** + * Helper method to create a validation event when output is not explicitly defined. + * + * @param id Shape ID of the operation. + * @param location Where the operation is defined. + * @return Returns the event. + */ + static ValidationEvent createOperationMissingOutput(ShapeId id, SourceLocation location) { + return ValidationEvent.builder() + .id("OperationMissingOutput") + .severity(Severity.WARNING) + .shapeId(id) + .sourceLocation(location) + .message("This operation does not define output, so it defaults to smithy.api#Unit. Defining output " + + "for an operation, even if you don't need it today, future-proofs the operation by " + + "allowing output members to be added later.") + .build(); + } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelAssembler.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelAssembler.java index 149b4b9bde2..30324e7a18a 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelAssembler.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelAssembler.java @@ -620,12 +620,15 @@ private List createModelFiles() { private ValidatedResult validate(Model model, TraitContainer traits, List events) { validateTraits(model.getShapeIds(), traits, events); - events.forEach(validationEventListener); // If ERROR validation events occur while loading, then performing more // granular semantic validation will only obscure the root cause of errors. if (disableValidation || LoaderUtils.containsErrorEvents(events)) { - return new ValidatedResult<>(model, events); + // Only return and emit events for errors. + return new ValidatedResult<>(model, events.stream() + .filter(event -> event.getSeverity() == Severity.ERROR) + .peek(validationEventListener) + .collect(Collectors.toList())); } // Validate the model based on the explicit validators and model metadata. @@ -634,10 +637,10 @@ private ValidatedResult validate(Model model, TraitContainer traits, List .validators(validators) .validatorFactory(validatorFactory) .eventListener(validationEventListener) + .includeEvents(events) .createValidator() .validate(model); - mergedEvents.addAll(events); return new ValidatedResult<>(model, mergedEvents); } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelValidator.java index 819fd7ef2a6..f310d8de2d8 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelValidator.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelValidator.java @@ -24,6 +24,7 @@ import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; +import java.util.stream.Stream; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.model.node.ObjectNode; @@ -65,6 +66,7 @@ private static final class LazyValidatorFactoryHolder { private final List validators = new ArrayList<>(); private final List suppressions = new ArrayList<>(); + private final List includeEvents = new ArrayList<>(); private ValidatorFactory validatorFactory; private Consumer eventListener; @@ -139,6 +141,21 @@ public ModelValidator eventListener(Consumer eventListener) { return this; } + /** + * Includes a set of events that were already encountered in the result. + * + *

The included events may be suppressed if they match any registered + * suppressions or suppressions loaded from the model. + * + * @param events Events to include. + * @return Returns the ModelValidator. + */ + public ModelValidator includeEvents(List events) { + this.includeEvents.clear(); + this.includeEvents.addAll(events); + return this; + } + /** * Creates a reusable Model Validator that uses every registered validator, * suppression, and extracts validators and suppressions from each @@ -176,9 +193,10 @@ public Validator createValidator() { return coreEvents; } - List result = modelValidators - .parallelStream() - .flatMap(validator -> validator.validate(model).stream()) + Stream eventStream = Stream.concat( + includeEvents.stream(), + modelValidators.parallelStream().flatMap(validator -> validator.validate(model).stream())); + List result = eventStream .filter(ModelValidator::filterPrelude) .map(event -> suppressEvent(model, event, modelSuppressions)) // Emit events as they occur during validation. diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborVisitor.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborVisitor.java index 4000d5d94b8..ed2480198c2 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborVisitor.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborVisitor.java @@ -153,8 +153,8 @@ private void addServiceAndResourceBindings(List result, ResourceSh @Override public List operationShape(OperationShape shape) { List result = new ArrayList<>(); - shape.getInput().ifPresent(id -> result.add(relationship(shape, RelationshipType.INPUT, id))); - shape.getOutput().ifPresent(id -> result.add(relationship(shape, RelationshipType.OUTPUT, id))); + result.add(relationship(shape, RelationshipType.INPUT, shape.getInputShape())); + result.add(relationship(shape, RelationshipType.OUTPUT, shape.getOutputShape())); for (ShapeId errorId : shape.getErrors()) { result.add(relationship(shape, RelationshipType.ERROR, errorId)); } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/PathFinder.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/PathFinder.java index aa50cfe5edd..8be74a2e123 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/PathFinder.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/PathFinder.java @@ -165,11 +165,8 @@ private Optional createPathTo(ToShapeId operationId, String memberName, Re return Optional.empty(); } - Optional structId = rel == RelationshipType.INPUT ? operation.getInput() : operation.getOutput(); - StructureShape struct = structId - .flatMap(model::getShape) - .flatMap(Shape::asStructureShape) - .orElse(null); + ShapeId structId = rel == RelationshipType.INPUT ? operation.getInputShape() : operation.getOutputShape(); + StructureShape struct = model.getShape(structId).flatMap(Shape::asStructureShape).orElse(null); if (struct == null) { return Optional.empty(); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java index 49c32ac9599..b7bc3f420a4 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java @@ -216,8 +216,8 @@ public Node mapShape(MapShape shape) { @Override public Node operationShape(OperationShape shape) { return withTraits(shape, createTypedNode(shape) - .withOptionalMember("input", shape.getInput().map(this::serializeReference)) - .withOptionalMember("output", shape.getOutput().map(this::serializeReference)) + .withMember("input", serializeReference(shape.getInputShape())) + .withMember("output", serializeReference(shape.getOutputShape())) .withOptionalMember("errors", createOptionalIdList(shape.getErrors()))) .build(); } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java index 74b3c500e4c..c1b2ad14ea8 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java @@ -23,6 +23,7 @@ import java.util.Optional; import java.util.Set; import software.amazon.smithy.model.knowledge.OperationIndex; +import software.amazon.smithy.model.traits.UnitTypeTrait; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.ToSmithyBuilder; @@ -37,8 +38,8 @@ public final class OperationShape extends Shape implements ToSmithyBuilder asOperationShape() { /** *

Gets the optional shape ID of the input of the operation.

* + *

For backward compatibility, if the input targets {@code smithy.api#Unit}, + * then an empty optional is returned. + * * @return Returns the optional shape ID. + * @deprecated Use {@link #getInputShape()} */ + @Deprecated public Optional getInput() { - return Optional.ofNullable(input); + return input.equals(UnitTypeTrait.UNIT) ? Optional.empty() : Optional.of(input); } /** *

Gets the optional shape ID of the output of the operation.

* + *

For backward compatibility, if the output targets {@code smithy.api#Unit}, + * then an empty optional is returned. + * * @return Returns the optional shape ID. + * @deprecated Use {@link #getOutputShape()} */ + @Deprecated public Optional getOutput() { - return Optional.ofNullable(output); + return output.equals(UnitTypeTrait.UNIT) ? Optional.empty() : Optional.of(output); + } + + /** + * Gets the input of the operation. + * + *

All operations have input, and they default to target + * {@code smithy.api#Unit}. + * + * @return Returns the non-nullable input. + */ + public ShapeId getInputShape() { + return input; + } + + /** + * Gets the output of the operation. + * + *

All operations have output, and they default to target + * {@code smithy.api#Unit}. + * + * @return Returns the non-nullable output. + */ + public ShapeId getOutputShape() { + return output; } /** @@ -125,8 +160,8 @@ public boolean equals(Object other) { return false; } else { OperationShape otherShape = (OperationShape) other; - return Objects.equals(input, otherShape.input) - && Objects.equals(output, otherShape.output) + return input.equals(otherShape.input) + && output.equals(otherShape.output) && errors.equals(otherShape.errors); } } @@ -135,8 +170,8 @@ public boolean equals(Object other) { * Builder used to create a {@link OperationShape}. */ public static final class Builder extends AbstractShapeBuilder { - private ShapeId input; - private ShapeId output; + private ShapeId input = UnitTypeTrait.UNIT; + private ShapeId output = UnitTypeTrait.UNIT; private final List errors = new ArrayList<>(); @Override @@ -148,11 +183,10 @@ public ShapeType getShapeType() { * Sets the input shape ID of the operation. * * @param inputShape Shape ID that MUST reference a structure. - * Set to null to clear. * @return Returns the builder. */ public Builder input(ToShapeId inputShape) { - input = inputShape == null ? null : inputShape.toShapeId(); + input = inputShape == null ? UnitTypeTrait.UNIT : inputShape.toShapeId(); return this; } @@ -160,11 +194,10 @@ public Builder input(ToShapeId inputShape) { * Sets the output shape ID of the operation. * * @param outputShape Shape ID that MUST reference a structure. - * Set to null to clear. * @return Returns the builder. */ public Builder output(ToShapeId outputShape) { - output = outputShape == null ? null : outputShape.toShapeId(); + output = outputShape == null ? UnitTypeTrait.UNIT : outputShape.toShapeId(); return this; } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java index 502c8d53fd6..a1ec32307f5 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java @@ -475,23 +475,14 @@ public Void resourceShape(ResourceShape shape) { @Override public Void operationShape(OperationShape shape) { serializeTraits(shape); - if (isEmptyOperation(shape)) { - codeWriter.write("operation $L {}", shape.getId().getName()).write(""); - return null; - } - codeWriter.openBlock("operation $L {", shape.getId().getName()); - shape.getInput().ifPresent(shapeId -> codeWriter.write("input: $I,", shapeId)); - shape.getOutput().ifPresent(shapeId -> codeWriter.write("output: $I,", shapeId)); + codeWriter.write("input: $I,", shape.getInputShape()); + codeWriter.write("output: $I,", shape.getOutputShape()); codeWriter.writeOptionalIdList("errors", shape.getErrors()); codeWriter.closeBlock("}"); codeWriter.write(""); return null; } - - private boolean isEmptyOperation(OperationShape shape) { - return !(shape.getInput().isPresent() || shape.getOutput().isPresent() || !shape.getErrors().isEmpty()); - } } /** diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/InputTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/InputTrait.java new file mode 100644 index 00000000000..74c4669afc1 --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/InputTrait.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.model.traits; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; + +/** + * Specializes a structure as the input of a single operation. + */ +public final class InputTrait extends AnnotationTrait { + public static final ShapeId ID = ShapeId.from("smithy.api#input"); + + public InputTrait(ObjectNode node) { + super(ID, node); + } + + public InputTrait() { + this(Node.objectNode()); + } + + public static final class Provider extends AnnotationTrait.Provider { + public Provider() { + super(ID, InputTrait::new); + } + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/OutputTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/OutputTrait.java new file mode 100644 index 00000000000..002dee199ac --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/OutputTrait.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.model.traits; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; + +/** + * Specializes a structure as the output of a single operation. + */ +public final class OutputTrait extends AnnotationTrait { + public static final ShapeId ID = ShapeId.from("smithy.api#output"); + + public OutputTrait(ObjectNode node) { + super(ID, node); + } + + public OutputTrait() { + this(Node.objectNode()); + } + + public static final class Provider extends AnnotationTrait.Provider { + public Provider() { + super(ID, OutputTrait::new); + } + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/UnitTypeTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/UnitTypeTrait.java new file mode 100644 index 00000000000..a90ab6d5235 --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/UnitTypeTrait.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.model.traits; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; + +public final class UnitTypeTrait extends AnnotationTrait { + + /** The unitType shape ID. */ + public static final ShapeId ID = ShapeId.from("smithy.api#unitType"); + + /** The shape ID of the built-in Unit type that uses this trait. */ + public static final ShapeId UNIT = ShapeId.from("smithy.api#Unit"); + + public UnitTypeTrait(ObjectNode node) { + super(ID, node); + } + + public UnitTypeTrait() { + this(Node.objectNode()); + } + + public static final class Provider extends AnnotationTrait.Provider { + public Provider() { + super(ID, UnitTypeTrait::new); + } + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/transform/plugins/CleanOperationStructures.java b/smithy-model/src/main/java/software/amazon/smithy/model/transform/plugins/CleanOperationStructures.java index a89b61b9f00..71a7e16f5c6 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/transform/plugins/CleanOperationStructures.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/transform/plugins/CleanOperationStructures.java @@ -50,17 +50,21 @@ private Collection getModifiedOperations(Model model, Collection r } private OperationShape transformInput(Collection removed, OperationShape operation) { - return operation.getInput() - .filter(id -> removed.stream().anyMatch(s -> s.getId().equals(id))) - .map(shape -> operation.toBuilder().input(null).build()) - .orElse(operation); + for (Shape remove : removed) { + if (remove.getId().equals(operation.getInputShape())) { + return operation.toBuilder().input(null).build(); + } + } + return operation; } private OperationShape transformOutput(Collection removed, OperationShape operation) { - return operation.getOutput() - .filter(id -> removed.stream().anyMatch(s -> s.getId().equals(id))) - .map(shape -> operation.toBuilder().output(null).build()) - .orElse(operation); + for (Shape remove : removed) { + if (remove.getId().equals(operation.getOutputShape())) { + return operation.toBuilder().output(null).build(); + } + } + return operation; } private OperationShape transformErrors(Collection removed, OperationShape operation) { diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/testrunner/SmithyTestCase.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/testrunner/SmithyTestCase.java index d2c1dd52ef4..674ef32930e 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/testrunner/SmithyTestCase.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/testrunner/SmithyTestCase.java @@ -115,6 +115,9 @@ public Result createResult(ValidatedResult validatedResult) { List extraEvents = actualEvents.stream() .filter(actualEvent -> getExpectedEvents().stream() .noneMatch(expectedEvent -> compareEvents(expectedEvent, actualEvent))) + // Exclude suppressed events from needing to be defined as acceptable validation + // events. However, these can still be defined as required events. + .filter(event -> event.getSeverity() != Severity.SUPPRESSED) .collect(Collectors.toList()); return new SmithyTestCase.Result(getModelLocation(), unmatchedEvents, extraEvents); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/ExamplesTraitValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/ExamplesTraitValidator.java index 4d22b548f4d..fe6aee166f8 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/ExamplesTraitValidator.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/ExamplesTraitValidator.java @@ -47,29 +47,17 @@ private List validateExamples(Model model, OperationShape shape List examples = trait.getExamples(); for (ExamplesTrait.Example example : examples) { - if (shape.getInput().isPresent()) { - model.getShape(shape.getInput().get()).ifPresent(input -> { - NodeValidationVisitor validator = createVisitor( - "input", example.getInput(), model, shape, example); - events.addAll(input.accept(validator)); - }); - } else if (!example.getInput().isEmpty()) { - events.add(error(shape, trait, String.format( - "Input parameters provided for operation with no input structure members: `%s`", - example.getTitle()))); - } + model.getShape(shape.getInputShape()).ifPresent(input -> { + NodeValidationVisitor validator = createVisitor( + "input", example.getInput(), model, shape, example); + events.addAll(input.accept(validator)); + }); - if (shape.getOutput().isPresent()) { - model.getShape(shape.getOutput().get()).ifPresent(output -> { - NodeValidationVisitor validator = createVisitor( - "output", example.getOutput(), model, shape, example); - events.addAll(output.accept(validator)); - }); - } else if (!example.getOutput().isEmpty()) { - events.add(error(shape, trait, String.format( - "Output parameters provided for operation with no output structure members: `%s`", - example.getTitle()))); - } + model.getShape(shape.getOutputShape()).ifPresent(output -> { + NodeValidationVisitor validator = createVisitor( + "output", example.getOutput(), model, shape, example); + events.addAll(output.accept(validator)); + }); if (example.getError().isPresent()) { ExamplesTrait.ErrorExample errorExample = example.getError().get(); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HostLabelTraitValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HostLabelTraitValidator.java index 935e53322c2..921dbeaf11b 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HostLabelTraitValidator.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HostLabelTraitValidator.java @@ -88,20 +88,11 @@ private List validateStructure( // Validate the host prefix SHOULD end in a period if it has labels. validateTrailingPeriod(operation, endpoint).ifPresent(events::add); - // If the operation has labels then it must also have input. - if (!operation.getInput().isPresent() && !endpoint.getHostPrefix().getLabels().isEmpty()) { - events.add(error(operation, endpoint, format( - "`endpoint` trait hostPrefix contains labels (%s), but operation has no input.", - ValidationUtils.tickedList(endpoint.getHostPrefix().getLabels().stream() - .map(SmithyPattern.Segment::getContent).collect(Collectors.toSet()))))); - } else { - // Only validate the bindings if the input is a structure. Typing - // validation of the input is handled elsewhere. - operation.getInput() - .flatMap(model::getShape) - .flatMap(Shape::asStructureShape) - .ifPresent(input -> events.addAll(validateBindings(operation, endpoint, input))); - } + // Only validate the bindings if the input is a structure. Typing + // validation of the input is handled elsewhere. + model.getShape(operation.getInputShape()) + .flatMap(Shape::asStructureShape) + .ifPresent(input -> events.addAll(validateBindings(operation, endpoint, input))); return events; } @@ -135,10 +126,10 @@ private List validateBindings( } if (!labels.isEmpty()) { - events.add(error(input, format( - "This structure is used as the input for the `%s` operation, but the following host labels " - + "found in the operation's `endpoint` trait do not have a corresponding member marked with the " - + "`hostLabel` trait: %s", operation.getId(), ValidationUtils.tickedList(labels)))); + events.add(error(operation, format( + "This operation uses %s as input, but the following host labels found in the operation's " + + "`endpoint` trait do not have a corresponding member marked with the `hostLabel` trait: %s", + input.getId(), ValidationUtils.tickedList(labels)))); } return events; diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpLabelTraitValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpLabelTraitValidator.java index 83cff7a8b42..ea69ac22c26 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpLabelTraitValidator.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpLabelTraitValidator.java @@ -64,17 +64,10 @@ public List validate(Model model) { } private List validateStructure(Model model, OperationShape operation, HttpTrait http) { - // If the operation has labels then it must also have input. - if (!operation.getInput().isPresent() && !http.getUri().getLabels().isEmpty()) { - return ListUtils.of(error(operation, http, String.format( - "`http` trait uri contains labels (%s), but operation has no input.", - ValidationUtils.tickedList(http.getUri().getLabels().stream() - .map(UriPattern.Segment::getContent).collect(Collectors.toSet()))))); - } - // Only continue validating if the input is a structure. Typing // validation of the input is handled elsewhere. - return operation.getInput().flatMap(model::getShape).flatMap(Shape::asStructureShape) + return model.getShape(operation.getInputShape()) + .flatMap(Shape::asStructureShape) .map(input -> validateBindings(model, operation, http, input)) .orElse(ListUtils.of()); } @@ -120,10 +113,10 @@ private List validateBindings( } if (!labels.isEmpty()) { - events.add(error(input, String.format( - "This structure is used as the input for the `%s` operation, but the following URI labels " - + "found in the operation's `http` trait do not have a corresponding member marked with the " - + "`httpLabel` trait: %s", operation.getId(), ValidationUtils.tickedList(labels)))); + events.add(error(operation, String.format( + "This operation uses `%s` as input, but the following URI labels found in the operation's " + + "`http` trait do not have a corresponding member marked with the `httpLabel` trait: %s", + input.getId(), ValidationUtils.tickedList(labels)))); } return events; diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpPayloadValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpPayloadValidator.java index bb7b6e6d646..f38189c286a 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpPayloadValidator.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpPayloadValidator.java @@ -70,10 +70,8 @@ private List validateOperation( OperationShape shape ) { List events = new ArrayList<>(); - opIndex.getInput(shape.getId()).flatMap(struct -> validatePayload(shape.getId(), struct, bindings, true)) - .ifPresent(events::add); - opIndex.getOutput(shape.getId()).flatMap(struct -> validatePayload(shape.getId(), struct, bindings, false)) - .ifPresent(events::add); + validatePayload(shape.getId(), opIndex.expectInputShape(shape), bindings, true).ifPresent(events::add); + validatePayload(shape.getId(), opIndex.expectOutputShape(shape), bindings, false).ifPresent(events::add); return events; } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpUriConflictValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpUriConflictValidator.java index 52f93c3e43a..6646fdf2d2b 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpUriConflictValidator.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpUriConflictValidator.java @@ -17,9 +17,9 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.BiFunction; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -35,7 +35,6 @@ import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.traits.EndpointTrait; import software.amazon.smithy.model.traits.HostLabelTrait; import software.amazon.smithy.model.traits.HttpTrait; @@ -206,14 +205,13 @@ private Map getHttpLabelPatterns(Model model, OperationShape op } private Map getHostLabelPatterns(Model model, OperationShape operation) { - Optional input = OperationIndex.of(model).getInput(operation); - return input.map(structureShape -> structureShape.members().stream() - .filter(member -> member.hasTrait(HostLabelTrait.class)) - .filter(member -> member.hasTrait(PatternTrait.class)) - .collect(Collectors.toMap( - MemberShape::getMemberName, - shape -> shape.expectTrait(PatternTrait.class).getPattern()))) - .orElse(Collections.emptyMap()); + Map result = new HashMap<>(); + for (MemberShape member : OperationIndex.of(model).expectInputShape(operation).getAllMembers().values()) { + if (member.hasTrait(HostLabelTrait.class) && member.hasTrait(PatternTrait.class)) { + result.put(member.getMemberName(), member.expectTrait(PatternTrait.class).getPattern()); + } + } + return result; } private String formatConflicts(UriPattern pattern, List> conflicts) { diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/OperationValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/OperationValidator.java new file mode 100644 index 00000000000..56bd2d567b9 --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/OperationValidator.java @@ -0,0 +1,234 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.model.validation.validators; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.NeighborProviderIndex; +import software.amazon.smithy.model.knowledge.OperationIndex; +import software.amazon.smithy.model.neighbor.NeighborProvider; +import software.amazon.smithy.model.neighbor.Relationship; +import software.amazon.smithy.model.neighbor.RelationshipType; +import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.InputTrait; +import software.amazon.smithy.model.traits.OutputTrait; +import software.amazon.smithy.model.traits.UnitTypeTrait; +import software.amazon.smithy.model.validation.AbstractValidator; +import software.amazon.smithy.model.validation.Severity; +import software.amazon.smithy.model.validation.ValidationEvent; +import software.amazon.smithy.model.validation.ValidationUtils; +import software.amazon.smithy.utils.ListUtils; + +/** + * Validates operation inputs, outputs, and the use of {@code input} + * and {@code output} traits. + * + *

    + *
  • Emits an {@code OperationMissingInputTrait} WARNING when the input + * of an operation is not marked with the {@code input} trait.
  • + *
  • Emits an {@code OperationMissingOutputTrait} WARNING when the + * output of an operation is not marked with the {@code output} trait.
  • + *
  • Emits an {@code OperationInputOutputMisuse} ERROR when a structure + * marked with the {@code input} trait or {@code output} trait is used in + * other contexts than input or output, or reused by multiple operations.
  • + *
  • Emits an {@code OperationInputOutputName} WARNING when the input or + * output shape name does not start with the name of the operation that + * targets it (if any).
  • + *
  • Emits an {@code OperationNameAmbiguity} WARNING when a shape has + * name that starts with the name of an operation and the name ends with + * Input, Output, Request, or Response but is not used as the input or + * output of an operation.
  • + *
+ */ +public final class OperationValidator extends AbstractValidator { + + private static final String OPERATION_INPUT_OUTPUT_MISUSE = "OperationInputOutputMisuse"; + private static final String OPERATION_INPUT_OUTPUT_NAME = "OperationInputOutputName"; + private static final String OPERATION_MISSING_INPUT_TRAIT = "OperationMissingInputTrait"; + private static final String OPERATION_MISSING_OUTPUT_TRAIT = "OperationMissingOutputTrait"; + private static final String OPERATION_NAME_AMBIGUITY = "OperationNameAmbiguity"; + private static final List INPUT_SUFFIXES = ListUtils.of("Input", "Request"); + private static final List OUTPUT_SUFFIXES = ListUtils.of("Output", "Response"); + + @Override + public List validate(Model model) { + List events = new ArrayList<>(); + NeighborProvider reverseProvider = NeighborProviderIndex.of(model).getReverseProvider(); + validateInputOutput(model.getShapesWithTrait(InputTrait.class), reverseProvider, events, "input", "output"); + validateInputOutput(model.getShapesWithTrait(OutputTrait.class), reverseProvider, events, "output", "input"); + + OperationIndex index = OperationIndex.of(model); + for (OperationShape operation : model.getOperationShapes()) { + StructureShape input = index.expectInputShape(operation); + StructureShape output = index.expectOutputShape(operation); + validateInputOutputSet(operation, input, output, events); + validateOperationNameAmbiguity(model, operation, events); + } + + return events; + } + + private void validateInputOutput( + Set shapes, + NeighborProvider reverseProvider, + List events, + String descriptor, + String invalid + ) { + for (Shape shape : shapes) { + Set operations = new HashSet<>(); + for (Relationship rel : reverseProvider.getNeighbors(shape)) { + String relName = rel.getSelectorLabel().orElse(""); + if (relName.equals(descriptor)) { + // Ensure there's one input/output target. + operations.add(rel.getShape().getId()); + // Make sure the operation name is part of the target shape ID. + if (!rel.getNeighborShapeId().getName().startsWith(rel.getShape().getId().getName())) { + events.add(emitBadInputOutputName(rel.getShape(), descriptor, rel.getNeighborShapeId())); + } + } else if (relName.equals(invalid)) { + // Input shouldn't reference output, and vice versa. + events.add(emitInvalidOperationBinding(rel.getShape(), descriptor, invalid)); + } else if (rel.getRelationshipType() == RelationshipType.MEMBER_TARGET) { + // Members can't target shapes marked with @input or @output. + events.add(emitInvalidMemberRef(rel.getShape().asMemberShape().get(), descriptor)); + } + } + + // Only a single shape can target an @input|@output shape. + if (operations.size() > 1) { + events.add(emitMultipleUses(shape, descriptor, operations)); + } + } + } + + private ValidationEvent emitInvalidOperationBinding(Shape operation, String property, String invalid) { + return ValidationEvent.builder() + .id(OPERATION_INPUT_OUTPUT_MISUSE) + .severity(Severity.ERROR) + .shape(operation) + .message("Operation " + property + " cannot target structures marked with the @" + invalid + " trait") + .build(); + } + + private ValidationEvent emitInvalidMemberRef(MemberShape member, String trait) { + return ValidationEvent.builder() + .id(OPERATION_INPUT_OUTPUT_MISUSE) + .severity(Severity.ERROR) + .shape(member) + .message("Members cannot target structures marked with the @" + trait + " trait: " + member.getTarget()) + .build(); + } + + private ValidationEvent emitMultipleUses(Shape shape, String descriptor, Set operations) { + return ValidationEvent.builder() + .id(OPERATION_INPUT_OUTPUT_MISUSE) + .severity(Severity.ERROR) + .shape(shape) + .message("Shapes marked with the @" + descriptor + " trait cannot be used as " + descriptor + " by " + + "multiple operations: " + ValidationUtils.tickedList(operations)) + .build(); + } + + private ValidationEvent emitBadInputOutputName(Shape operation, String property, ShapeId target) { + return ValidationEvent.builder() + .severity(Severity.WARNING) + .shape(operation) + .id(OPERATION_INPUT_OUTPUT_NAME) + .message(String.format( + "The %s of this operation should target a shape that starts with the operation's name, '%s', " + + "but the targeted shape is `%s`", property, operation.getId().getName(), target)) + .build(); + } + + private void validateInputOutputSet( + OperationShape operation, + StructureShape input, + StructureShape output, + List events + ) { + if (!input.getId().equals(UnitTypeTrait.UNIT) && !input.hasTrait(InputTrait.class)) { + events.add(ValidationEvent.builder() + .id(OPERATION_MISSING_INPUT_TRAIT) + .severity(Severity.WARNING) + .shape(input) + .message(String.format( + "This structure is the input of `%s`, but it is not marked with the " + + "@input trait. The @input trait gives operations more flexibility to " + + "evolve their top-level input members in ways that would otherwise " + + "be backward incompatible.", operation.getId())) + .build()); + } + + if (!output.getId().equals(UnitTypeTrait.UNIT) && !output.hasTrait(OutputTrait.class)) { + events.add(ValidationEvent.builder() + .id(OPERATION_MISSING_OUTPUT_TRAIT) + .severity(Severity.WARNING) + .shape(output) + .message(String.format( + "This structure is the output of `%s`, but it is not marked with " + + "the @output trait.", operation.getId())) + .build()); + } + } + + private void validateOperationNameAmbiguity(Model model, OperationShape operation, List events) { + ShapeId input = operation.getInputShape(); + for (String suffix : INPUT_SUFFIXES) { + ShapeId test = ShapeId.from(operation.getId().toShapeId() + suffix); + if (!test.equals(input)) { + model.getShape(test).ifPresent(ambiguousShape -> { + events.add(createAmbiguousEvent(ambiguousShape, operation, input, "input")); + }); + } + } + + ShapeId output = operation.getOutputShape(); + for (String suffix : OUTPUT_SUFFIXES) { + ShapeId test = ShapeId.from(operation.getId().toShapeId() + suffix); + if (!test.equals(output)) { + model.getShape(test).ifPresent(ambiguousShape -> { + events.add(createAmbiguousEvent(ambiguousShape, operation, output, "output")); + }); + } + } + } + + private ValidationEvent createAmbiguousEvent( + Shape ambiguousShape, + OperationShape operation, + ShapeId ioShape, + String descriptor + ) { + return ValidationEvent.builder() + .id(OPERATION_NAME_AMBIGUITY) + .shape(ambiguousShape) + .severity(Severity.WARNING) + .message(String.format( + "The name of this shape implies that it is the %1$s of %2$s, but that operation uses %3$s " + + "for %1$s. This kind of ambiguity can confuse developers calling this operation and can " + + "cause issues in code generators that use similar naming conventions to generate %1$s " + + "types.", descriptor, operation.getId(), ioShape)) + .build(); + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/PaginatedTraitValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/PaginatedTraitValidator.java index 1457160c891..9f778b83b52 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/PaginatedTraitValidator.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/PaginatedTraitValidator.java @@ -85,25 +85,19 @@ private List validateOperation( ) { List events = new ArrayList<>(); - if (!opIndex.getInput(operation).isPresent()) { - events.add(error(operation, trait, "paginated operations require an input")); - } else { - events.addAll(validateMember(opIndex, model, null, operation, trait, new InputTokenValidator())); - PageSizeValidator pageSizeValidator = new PageSizeValidator(); - events.addAll(validateMember(opIndex, model, null, operation, trait, pageSizeValidator)); - pageSizeValidator.getMember(model, opIndex, operation, trait) - .filter(MemberShape::isRequired) - .ifPresent(member -> events.add(warning(operation, trait, String.format( - "paginated trait `%s` member `%s` should not be required", - pageSizeValidator.propertyName(), member.getMemberName())))); - } - - if (!opIndex.getOutput(operation).isPresent()) { - events.add(error(operation, trait, "paginated operations require an output")); - } else { - events.addAll(validateMember(opIndex, model, null, operation, trait, new OutputTokenValidator())); - events.addAll(validateMember(opIndex, model, null, operation, trait, new ItemValidator())); - } + // Validate input. + events.addAll(validateMember(opIndex, model, null, operation, trait, new InputTokenValidator())); + PageSizeValidator pageSizeValidator = new PageSizeValidator(); + events.addAll(validateMember(opIndex, model, null, operation, trait, pageSizeValidator)); + pageSizeValidator.getMember(model, opIndex, operation, trait) + .filter(MemberShape::isRequired) + .ifPresent(member -> events.add(warning(operation, trait, String.format( + "paginated trait `%s` member `%s` should not be required", + pageSizeValidator.propertyName(), member.getMemberName())))); + + // Validate output. + events.addAll(validateMember(opIndex, model, null, operation, trait, new OutputTokenValidator())); + events.addAll(validateMember(opIndex, model, null, operation, trait, new ItemValidator())); if (events.isEmpty()) { model.shapes(ServiceShape.class).forEach(svc -> { @@ -224,9 +218,9 @@ boolean pathsAllowed() { Optional getMember( Model model, OperationIndex opIndex, OperationShape operation, PaginatedTrait trait ) { - Optional outputShape = opIndex.getOutput(operation); - return outputShape.flatMap(structureShape -> getMemberPath(opIndex, operation, trait) - .map(path -> PaginatedTrait.resolveFullPath(path, model, structureShape))) + StructureShape outputShape = opIndex.expectOutputShape(operation); + return getMemberPath(opIndex, operation, trait) + .map(path -> PaginatedTrait.resolveFullPath(path, model, outputShape)) .flatMap(memberShapes -> { if (memberShapes.size() == 0) { return Optional.empty(); @@ -264,8 +258,8 @@ Optional getMemberPath(OperationIndex opIndex, OperationShape operation, Optional getMember( Model model, OperationIndex opIndex, OperationShape operation, PaginatedTrait trait ) { - return getMemberPath(opIndex, operation, trait) - .flatMap(memberName -> opIndex.getInput(operation).flatMap(input -> input.getMember(memberName))); + StructureShape input = opIndex.expectInputShape(operation); + return getMemberPath(opIndex, operation, trait).flatMap(input::getMember); } } @@ -319,8 +313,8 @@ Optional getMemberPath(OperationIndex opIndex, OperationShape operation, Optional getMember( Model model, OperationIndex opIndex, OperationShape operation, PaginatedTrait trait ) { - return getMemberPath(opIndex, operation, trait) - .flatMap(memberName -> opIndex.getInput(operation).flatMap(input -> input.getMember(memberName))); + StructureShape input = opIndex.expectInputShape(operation); + return getMemberPath(opIndex, operation, trait).flatMap(input::getMember); } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/UnitTypeValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/UnitTypeValidator.java new file mode 100644 index 00000000000..806320a82b1 --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/UnitTypeValidator.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.model.validation.validators; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.NeighborProviderIndex; +import software.amazon.smithy.model.neighbor.NeighborProvider; +import software.amazon.smithy.model.neighbor.Relationship; +import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeType; +import software.amazon.smithy.model.traits.UnitTypeTrait; +import software.amazon.smithy.model.validation.AbstractValidator; +import software.amazon.smithy.model.validation.ValidationEvent; + +/** + * Ensures that a unit type can only be referenced as operation + * input/output or by a tagged union member. + */ +public final class UnitTypeValidator extends AbstractValidator { + @Override + public List validate(Model model) { + NeighborProvider neighborProvider = NeighborProviderIndex.of(model).getReverseProvider(); + + Shape unit = model.getShape(UnitTypeTrait.UNIT).orElse(null); + if (unit == null) { + return Collections.emptyList(); + } + + List events = new ArrayList<>(); + for (Relationship relationship : neighborProvider.getNeighbors(unit)) { + switch (relationship.getRelationshipType()) { + case INPUT: + case OUTPUT: + break; + case MEMBER_TARGET: + relationship.getShape() + .asMemberShape() + .map(MemberShape::getContainer) + .flatMap(model::getShape) + .filter(shape -> shape.getType() != ShapeType.UNION) + .ifPresent(container -> { + events.add(error(relationship.getShape(), + "Only members of a union can reference smithy.api#Unit")); + }); + break; + default: + events.add(error(relationship.getShape(), String.format( + "This shape has an invalid %s reference to smithy.api#Unit", + relationship.getRelationshipType()))); + } + } + + return events; + } +} diff --git a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService index bdc807cd40c..518427a1f9c 100644 --- a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService +++ b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService @@ -29,12 +29,14 @@ software.amazon.smithy.model.traits.HttpTrait$Provider software.amazon.smithy.model.traits.IdempotencyTokenTrait$Provider software.amazon.smithy.model.traits.IdempotentTrait$Provider software.amazon.smithy.model.traits.IdRefTrait$Provider +software.amazon.smithy.model.traits.InputTrait$Provider software.amazon.smithy.model.traits.InternalTrait$Provider software.amazon.smithy.model.traits.JsonNameTrait$Provider software.amazon.smithy.model.traits.LengthTrait$Provider software.amazon.smithy.model.traits.MediaTypeTrait$Provider software.amazon.smithy.model.traits.NoReplaceTrait$Provider software.amazon.smithy.model.traits.OptionalAuthTrait$Provider +software.amazon.smithy.model.traits.OutputTrait$Provider software.amazon.smithy.model.traits.PaginatedTrait$Provider software.amazon.smithy.model.traits.PatternTrait$Provider software.amazon.smithy.model.traits.PrivateTrait$Provider @@ -57,6 +59,7 @@ software.amazon.smithy.model.traits.TimestampFormatTrait$Provider software.amazon.smithy.model.traits.TitleTrait$Provider software.amazon.smithy.model.traits.TraitDefinition$Provider software.amazon.smithy.model.traits.UniqueItemsTrait$Provider +software.amazon.smithy.model.traits.UnitTypeTrait$Provider software.amazon.smithy.model.traits.UnstableTrait$Provider software.amazon.smithy.model.traits.XmlAttributeTrait$Provider software.amazon.smithy.model.traits.XmlFlattenedTrait$Provider diff --git a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator index f76a4f5b53c..251203901b0 100644 --- a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator +++ b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator @@ -19,6 +19,7 @@ software.amazon.smithy.model.validation.validators.HttpUriConflictValidator software.amazon.smithy.model.validation.validators.LengthTraitValidator software.amazon.smithy.model.validation.validators.MediaTypeValidator software.amazon.smithy.model.validation.validators.NoInlineDocumentSupportValidator +software.amazon.smithy.model.validation.validators.OperationValidator software.amazon.smithy.model.validation.validators.PaginatedTraitValidator software.amazon.smithy.model.validation.validators.PrivateAccessValidator software.amazon.smithy.model.validation.validators.RangeTraitValidator @@ -38,6 +39,7 @@ software.amazon.smithy.model.validation.validators.TargetValidator software.amazon.smithy.model.validation.validators.TraitConflictValidator software.amazon.smithy.model.validation.validators.TraitTargetValidator software.amazon.smithy.model.validation.validators.TraitValueValidator +software.amazon.smithy.model.validation.validators.UnitTypeValidator software.amazon.smithy.model.validation.validators.UnreferencedShapeValidator software.amazon.smithy.model.validation.validators.UnstableTraitValidator software.amazon.smithy.model.validation.validators.XmlNamespaceTraitValidator diff --git a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy index 1886699a495..279555bc19c 100644 --- a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy +++ b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy @@ -51,6 +51,11 @@ double Double double PrimitiveDouble +/// The single unit type shape, similar to Void and None in other +/// languages, used to represent no meaningful value. +@unitType +structure Unit {} + // ------ Prelude traits /// Makes a shape a trait. @@ -770,3 +775,20 @@ list suppress { @unstable @trait(selector: "operation") structure httpChecksumRequired {} + +/// Specializes a structure for use only as the input of a single operation. +@trait(selector: "structure", conflicts: [output, error]) +@tags(["diff.error.const"]) +structure input {} + +/// Specializes a structure for use only as the output of a single operation. +@trait(selector: "structure", conflicts: [input, error]) +@tags(["diff.error.const"]) +structure output {} + +/// Specializes a structure as a unit type that has no meaningful value. +/// This trait is private, which ensures that only a single Unit shape +/// can be created, smithy.api#Unit. +@trait(selector: "[id=smithy.api#Unit]") +@internal +structure unitType {} diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/OperationIndexTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/OperationIndexTest.java index fa2963044b5..a7770907000 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/OperationIndexTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/OperationIndexTest.java @@ -31,6 +31,7 @@ import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.UnitTypeTrait; public class OperationIndexTest { @@ -50,11 +51,17 @@ public static void after() { } @Test - public void indexesEmptyOperations() { + public void indexesUnitOperations() { OperationIndex opIndex = OperationIndex.of(model); assertThat(opIndex.getInput(ShapeId.from("ns.foo#A")), is(Optional.empty())); + assertThat(opIndex.getInputShape(ShapeId.from("ns.foo#A")).map(Shape::getId), + equalTo(Optional.of(UnitTypeTrait.UNIT))); + assertThat(opIndex.expectInputShape(ShapeId.from("ns.foo#A")).getId(), equalTo(UnitTypeTrait.UNIT)); assertThat(opIndex.getOutput(ShapeId.from("ns.foo#A")), is(Optional.empty())); + assertThat(opIndex.getOutputShape(ShapeId.from("ns.foo#A")).map(Shape::getId), + equalTo(Optional.of(UnitTypeTrait.UNIT))); + assertThat(opIndex.expectOutputShape(ShapeId.from("ns.foo#A")).getId(), equalTo(UnitTypeTrait.UNIT)); assertThat(opIndex.getErrors(ShapeId.from("ns.foo#A")), empty()); } diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/traits/InputTraitTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/traits/InputTraitTest.java new file mode 100644 index 00000000000..756779ecec4 --- /dev/null +++ b/smithy-model/src/test/java/software/amazon/smithy/model/traits/InputTraitTest.java @@ -0,0 +1,25 @@ +package software.amazon.smithy.model.traits; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.ShapeId; + +public class InputTraitTest { + + @Test + public void loadsTrait() { + TraitFactory provider = TraitFactory.createServiceFactory(); + Optional trait = provider.createTrait( + ShapeId.from("smithy.api#input"), ShapeId.from("ns.qux#foo"), Node.objectNode()); + + assertTrue(trait.isPresent()); + assertThat(trait.get(), instanceOf(InputTrait.class)); + assertThat(trait.get().toNode(), equalTo(Node.objectNode())); + } +} diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/traits/OutputTraitTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/traits/OutputTraitTest.java new file mode 100644 index 00000000000..d62b0795be3 --- /dev/null +++ b/smithy-model/src/test/java/software/amazon/smithy/model/traits/OutputTraitTest.java @@ -0,0 +1,25 @@ +package software.amazon.smithy.model.traits; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.ShapeId; + +public class OutputTraitTest { + + @Test + public void loadsTrait() { + TraitFactory provider = TraitFactory.createServiceFactory(); + Optional trait = provider.createTrait( + ShapeId.from("smithy.api#output"), ShapeId.from("ns.qux#foo"), Node.objectNode()); + + assertTrue(trait.isPresent()); + assertThat(trait.get(), instanceOf(OutputTrait.class)); + assertThat(trait.get().toNode(), equalTo(Node.objectNode())); + } +} diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java index 643e1be433e..55a10732b39 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java @@ -30,6 +30,7 @@ import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.traits.EnumTrait; import software.amazon.smithy.model.traits.ReadonlyTrait; +import software.amazon.smithy.model.traits.UnitTypeTrait; public class ModelTransformerTest { @@ -41,10 +42,10 @@ public void discoversOnRemoveClassesWithSpi() { ShapeId operation = ShapeId.from("ns.foo#MyOperation"); assertThat(result.expectShape(operation), Matchers.not(Optional.empty())); - assertThat(result.expectShape(operation).asOperationShape().flatMap(OperationShape::getInput), - Matchers.is(Optional.empty())); - assertThat(result.expectShape(operation).asOperationShape().flatMap(OperationShape::getOutput), - Matchers.is(Optional.empty())); + assertThat(result.expectShape(operation).asOperationShape().map(OperationShape::getInputShape), + Matchers.equalTo(Optional.of(UnitTypeTrait.UNIT))); + assertThat(result.expectShape(operation).asOperationShape().map(OperationShape::getOutputShape), + Matchers.equalTo(Optional.of(UnitTypeTrait.UNIT))); assertThat(result.expectShape(operation).asOperationShape().map(OperationShape::getErrors), Matchers.equalTo(Optional.of(Collections.emptyList()))); } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/detects-bad-syntactic-shape-ids.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/detects-bad-syntactic-shape-ids.smithy index 188f8f9fe5c..7ea27a3d389 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/detects-bad-syntactic-shape-ids.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/detects-bad-syntactic-shape-ids.smithy @@ -1,4 +1,13 @@ namespace smithy.example @http(method: GET, uri: "/") // <- Use "GET" not GET! -operation Foo {} +operation Foo { + input: FooInput, + output: FooOutput +} + +@input +structure FooInput {} + +@output +structure FooOutput {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/dupe-operation-binding.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/dupe-operation-binding.smithy index 73c5c3aa7ee..722ad97c385 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/dupe-operation-binding.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/dupe-operation-binding.smithy @@ -5,4 +5,5 @@ operation GetFoo { input: GetFooInput, } +@input structure GetFooInput {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/suppression-test.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/suppression-test.smithy index 322c40a5b81..b19629fc6d3 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/suppression-test.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/loader/suppression-test.smithy @@ -62,13 +62,18 @@ service MyService { operation GetFoo { input: GetFooInput, + output: GetFooOutput } +@input structure GetFooInput { list1: List1, list2: List2, } +@output +structure GetFooOutput {} + list List1 { member: String, } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-invalid-shape-id.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-invalid-shape-id.json index 81a7a6982c9..891f30c3b26 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-invalid-shape-id.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-invalid-shape-id.json @@ -1,5 +1,11 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "ns.foo"}, + {"id": "OperationMissingOutput", "namespace": "ns.foo"} + ] + }, "shapes": { "ns.foo#InvalidShapeId": { "type": "operation", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-auth-definitions.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-auth-definitions.json index fcc95d893d3..db63fe99070 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-auth-definitions.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-auth-definitions.json @@ -1,5 +1,11 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "ns.foo"}, + {"id": "OperationMissingOutput", "namespace": "ns.foo"} + ] + }, "shapes": { "ns.foo#Valid1": { "type": "operation", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-service-schemes.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-service-schemes.json index fae8cd44135..6e5cf4ed92c 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-service-schemes.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-service-schemes.json @@ -1,5 +1,11 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "ns.foo"}, + {"id": "OperationMissingOutput", "namespace": "ns.foo"} + ] + }, "shapes": { "ns.foo#Service": { "type": "service", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-with-errors.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-with-errors.smithy index d6799801c0b..222a67ad8fd 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-with-errors.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-with-errors.smithy @@ -1,9 +1,14 @@ namespace smithy.example operation SubscribeToMovements { + input: SubscribeToMovementsInput, output: SubscribeToMovementsOutput } +@input +structure SubscribeToMovementsInput {} + +@output structure SubscribeToMovementsOutput { movements: MovementEvents, } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/examples-trait-validator.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/examples-trait-validator.errors index e5e6a1ca75a..9aaeb052042 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/examples-trait-validator.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/examples-trait-validator.errors @@ -1,5 +1,5 @@ -[ERROR] ns.foo#Operation2: Input parameters provided for operation with no input structure members: `Testing 3` | ExamplesTrait -[ERROR] ns.foo#Operation2: Output parameters provided for operation with no output structure members: `Testing 3` | ExamplesTrait +[WARNING] ns.foo#Operation2: Example input of `Testing 3`: Invalid structure member `foo` found for `smithy.api#Unit` | ExamplesTrait +[WARNING] ns.foo#Operation2: Example output of `Testing 3`: Invalid structure member `bam` found for `smithy.api#Unit` | ExamplesTrait [ERROR] ns.foo#Operation2: Error parameters provided for operation without the `ns.foo#OperationError` error: `Testing 3` | ExamplesTrait [ERROR] ns.foo#Operation: Example input of `Testing 2`: Missing required structure member `foo` for `ns.foo#OperationInput` | ExamplesTrait [WARNING] ns.foo#Operation: Example output of `Testing 2`: Invalid structure member `additional` found for `ns.foo#OperationOutput` | ExamplesTrait diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/examples-trait-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/examples-trait-validator.json index 1de6c6a622c..17486e4afbf 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/examples-trait-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/examples-trait-validator.json @@ -1,5 +1,11 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"} + ] + }, "shapes": { "ns.foo#Operation": { "type": "operation", @@ -68,6 +74,9 @@ "baz": { "target": "ns.foo#String" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#OperationOutput": { @@ -79,6 +88,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#Operation2": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/host-request-validator.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/host-request-validator.errors index 93ca8715579..9d8fd92d99e 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/host-request-validator.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/host-request-validator.errors @@ -1,6 +1,6 @@ -[ERROR] ns.foo#A: `endpoint` trait hostPrefix contains labels (`foo`), but operation has no input. | HostLabelTrait -[ERROR] ns.foo#BInput: This structure is used as the input for the `ns.foo#B` operation, but the following host labels found in the operation's `endpoint` trait do not have a corresponding member marked with the `hostLabel` trait: `foo` | HostLabelTrait -[ERROR] ns.foo#DInput: This structure is used as the input for the `ns.foo#D` operation, but the following host labels found in the operation's `endpoint` trait do not have a corresponding member marked with the `hostLabel` trait: `foo` | HostLabelTrait +[ERROR] ns.foo#A: This operation uses smithy.api#Unit as input, but the following host labels found in the operation's `endpoint` trait do not have a corresponding member marked with the `hostLabel` trait: `foo` | HostLabelTrait +[ERROR] ns.foo#B: This operation uses ns.foo#BInput as input, but the following host labels found in the operation's `endpoint` trait do not have a corresponding member marked with the `hostLabel` trait: `foo` | HostLabelTrait +[ERROR] ns.foo#D: This operation uses ns.foo#DInput as input, but the following host labels found in the operation's `endpoint` trait do not have a corresponding member marked with the `hostLabel` trait: `foo` | HostLabelTrait [ERROR] ns.foo#EInput$foo: Trait `hostLabel` cannot be applied to `ns.foo#EInput$foo`. This trait may only be applied to shapes that match the following selector: structure > :test(member[trait|required] > string) | TraitTarget [ERROR] ns.foo#FInput$foo: Trait `hostLabel` cannot be applied to `ns.foo#FInput$foo`. This trait may only be applied to shapes that match the following selector: structure > :test(member[trait|required] > string) | TraitTarget [ERROR] ns.foo#H: The `endpoint` trait hostPrefix, H.1234567890123456789012345678901234567890123456789012345678901234., could not expand in to a valid RFC 3986 host: H.1234567890123456789012345678901234567890123456789012345678901234. | HostLabelTrait diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/host-request-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/host-request-validator.json index 563e4cbb48b..1c32c164014 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/host-request-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/host-request-validator.json @@ -1,5 +1,13 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"}, + {"id": "OperationMissingInputTrait", "namespace": "*"}, + {"id": "OperationMissingOutputTrait", "namespace": "*"} + ] + }, "shapes": { "ns.foo#MyService": { "type": "service", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-api-key-scheme-trait-validator-test.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-api-key-scheme-trait-validator-test.json index 8934b5cc2c0..aaf6c11fcdf 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-api-key-scheme-trait-validator-test.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-api-key-scheme-trait-validator-test.json @@ -30,10 +30,16 @@ } }, "ns.foo#AInput": { - "type": "structure" + "type": "structure", + "traits": { + "smithy.api#input": {} + } }, "ns.foo#AOutput": { - "type": "structure" + "type": "structure", + "traits": { + "smithy.api#output": {} + } } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-bindings-missing-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-bindings-missing-validator.json index 18ac49d8f5f..9f28d6dc5e6 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-bindings-missing-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-bindings-missing-validator.json @@ -18,6 +18,8 @@ }, "ns.foo#HasBindings": { "type": "operation", + "input": {"target": "ns.foo#HasBindingsInput"}, + "output": {"target": "ns.foo#HasBindingsOutput"}, "traits": { "smithy.api#readonly": {}, "smithy.api#http": { @@ -26,17 +28,57 @@ } } }, + "ns.foo#HasBindingsInput": { + "type": "structure", + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#HasBindingsOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} + } + }, "ns.foo#MissingBindings1": { "type": "operation", + "input": {"target": "ns.foo#MissingBindings1Input"}, + "output": {"target": "ns.foo#MissingBindings1Output"}, "traits": { "smithy.api#readonly": {} } }, + "ns.foo#MissingBindings1Input": { + "type": "structure", + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#MissingBindings1Output": { + "type": "structure", + "traits": { + "smithy.api#output": {} + } + }, "ns.foo#MissingBindings2": { "type": "operation", + "input": {"target": "ns.foo#MissingBindings2Input"}, + "output": {"target": "ns.foo#MissingBindings2Output"}, "traits": { "smithy.api#readonly": {} } + }, + "ns.foo#MissingBindings2Input": { + "type": "structure", + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#MissingBindings2Output": { + "type": "structure", + "traits": { + "smithy.api#output": {} + } } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-checksum-required-trait.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-checksum-required-trait.json index ee90b63ff91..0755e81679f 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-checksum-required-trait.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-checksum-required-trait.json @@ -1,5 +1,13 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"}, + {"id": "OperationMissingInputTrait", "namespace": "*"}, + {"id": "OperationMissingOutputTrait", "namespace": "*"} + ] + }, "shapes": { "ns.foo#Blob": { "type": "blob" diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.json index cc299a99c48..1fdc352e789 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.json @@ -15,6 +15,9 @@ "input": { "target": "ns.foo#SayHelloInput" }, + "output": { + "target": "ns.foo#SayHelloOutput" + }, "traits": { "smithy.api#http": { "method": "POST", @@ -31,6 +34,15 @@ "smithy.api#httpHeader": "Not valid!" } } + }, + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#SayHelloOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-method-semantics-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-method-semantics-validator.json index b28bb880a97..76e871027f1 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-method-semantics-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-method-semantics-validator.json @@ -1,5 +1,11 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "ns.foo"}, + {"id": "OperationMissingOutput", "namespace": "ns.foo"} + ] + }, "shapes": { "ns.foo#A": { "type": "operation", @@ -120,6 +126,9 @@ "smithy.api#httpPayload": {} } } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#L": { @@ -150,6 +159,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#M": { @@ -171,6 +183,9 @@ "payload": { "target": "smithy.api#String" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#N": { @@ -198,7 +213,10 @@ }, "ns.foo#OptionsInput": { "type": "structure", - "members": {} + "members": {}, + "traits": { + "smithy.api#input": {} + } } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.errors index c5659de11a3..77c8c3c6443 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.errors @@ -1,5 +1,5 @@ -[ERROR] ns.foo#A: `http` trait uri contains labels (`foo`), but operation has no input. | HttpLabelTrait -[ERROR] ns.foo#BInput: This structure is used as the input for the `ns.foo#B` operation, but the following URI labels found in the operation's `http` trait do not have a corresponding member marked with the `httpLabel` trait: `d` | HttpLabelTrait +[ERROR] ns.foo#A: This operation uses `smithy.api#Unit` as input, but the following URI labels found in the operation's `http` trait do not have a corresponding member marked with the `httpLabel` trait: `foo` | HttpLabelTrait +[ERROR] ns.foo#B: This operation uses `ns.foo#BInput` as input, but the following URI labels found in the operation's `http` trait do not have a corresponding member marked with the `httpLabel` trait: `d` | HttpLabelTrait [ERROR] ns.foo#BadError: Trait `httpError` cannot be applied to `ns.foo#BadError`. This trait may only be applied to shapes that match the following selector: structure[trait|error] | TraitTarget [ERROR] ns.foo#BadErrorMultipleBindings$foo: Found conflicting traits on member shape: `httpHeader` conflicts with `httpPayload`, `httpPayload` conflicts with `httpHeader` | TraitConflict [ERROR] ns.foo#DInput$a: `httpHeader` binding of `x-foo` conflicts with the `httpPrefixHeaders` binding of `ns.foo#DInput$e` to `x-foo`. `httpHeader` bindings must not case-insensitively start with any `httpPrefixHeaders` bindings. | HttpPrefixHeadersTrait diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.json index 998d220f288..2d4d2eb149c 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-request-response-validator.json @@ -1,5 +1,13 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "ns.foo"}, + {"id": "OperationMissingOutput", "namespace": "ns.foo"}, + {"id": "OperationMissingInputTrait", "namespace": "ns.foo"}, + {"id": "OperationMissingOutputTrait", "namespace": "ns.foo"} + ] + }, "shapes": { "ns.foo#MyService": { "type": "service", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-response-code-semantics-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-response-code-semantics-validator.json index 934cdc6c159..5b677ca00a7 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-response-code-semantics-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-response-code-semantics-validator.json @@ -1,5 +1,11 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"} + ] + }, "shapes": { "ns.foo#A": { "type": "operation", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-trait-conflicts.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-trait-conflicts.json index a2004ca7743..132c83bda0b 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-trait-conflicts.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-trait-conflicts.json @@ -14,6 +14,9 @@ "smithy.api#httpHeader": "x-amz-foo" } } + }, + "traits": { + "smithy.api#input": {} } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-conflict-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-conflict-validator.json index 68e846b8999..a6f0c823b5b 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-conflict-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-uri-conflict-validator.json @@ -1,5 +1,13 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"}, + {"id": "OperationMissingInputTrait", "namespace": "*"}, + {"id": "OperationMissingOutputTrait", "namespace": "*"} + ] + }, "shapes": { "ns.foo#MyService": { "type": "service", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/valid-implicit-lifecycles.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/valid-implicit-lifecycles.json index 56135bd8d75..324cbdd25c7 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/valid-implicit-lifecycles.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/valid-implicit-lifecycles.json @@ -1,5 +1,13 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"}, + {"id": "OperationMissingInputTrait", "namespace": "*"}, + {"id": "OperationMissingOutputTrait", "namespace": "*"} + ] + }, "shapes": { "ns.foo#ValidA": { "type": "resource", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-idempotent-lifecycles.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-idempotent-lifecycles.json index 221e1cbaf5d..91657ae3237 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-idempotent-lifecycles.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-idempotent-lifecycles.json @@ -1,5 +1,13 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"}, + {"id": "OperationMissingInputTrait", "namespace": "*"}, + {"id": "OperationMissingOutputTrait", "namespace": "*"} + ] + }, "shapes": { "ns.foo#Resource": { "type": "resource", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-instance-and-collection-operations.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-instance-and-collection-operations.json index c147a362cd9..abea065f6a1 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-instance-and-collection-operations.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-instance-and-collection-operations.json @@ -1,5 +1,13 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"}, + {"id": "OperationMissingInputTrait", "namespace": "*"}, + {"id": "OperationMissingOutputTrait", "namespace": "*"} + ] + }, "shapes": { "ns.foo#ResourceA": { "type": "resource", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-readonly-lifecycles.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-readonly-lifecycles.json index ec2cbde8a98..1159bcffe13 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-readonly-lifecycles.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/lifecycle/validates-readonly-lifecycles.json @@ -1,5 +1,13 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"}, + {"id": "OperationMissingInputTrait", "namespace": "*"}, + {"id": "OperationMissingOutputTrait", "namespace": "*"} + ] + }, "shapes": { "ns.foo#Resource": { "type": "resource", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/linters/emit-each-selector-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/linters/emit-each-selector-validator.json index d311b4ad836..205f5bba678 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/linters/emit-each-selector-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/linters/emit-each-selector-validator.json @@ -96,6 +96,9 @@ "otherMemberName": { "target": "ns.foo#Integer" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#OperationAOutput": { @@ -104,6 +107,9 @@ "b": { "target": "ns.foo#Map" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#OperationErrorA": { @@ -122,6 +128,9 @@ "type": "operation", "input": { "target": "ns.foo#OperationBInput" + }, + "traits": { + "smithy.api#suppress": ["OperationMissingOutput"] } }, "ns.foo#OperationBInput": { @@ -133,6 +142,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#UtcTimestamp": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/model-validation/model-validation-stops-validating-on-core-errors.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/model-validation/model-validation-stops-validating-on-core-errors.smithy index 229d1185c91..2b85b01c0a6 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/model-validation/model-validation-stops-validating-on-core-errors.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/model-validation/model-validation-stops-validating-on-core-errors.smithy @@ -10,9 +10,10 @@ service Example { @http(method: "POST", uri: "/foo") operation Foo { input: FooRequest, - output: FooRequest + output: FooResponse } +@input @xmlName("CustomFooRequest") structure FooRequest { @xmlFlattened @@ -21,3 +22,6 @@ structure FooRequest { @xmlFlattened mapVal: MapOfInteger } + +@output +structure FooResponse {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.smithy index 973240013e5..c979a14ae13 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.smithy @@ -16,13 +16,18 @@ service FooService { } operation Foo { - input: FooInput + input: FooInput, + output: FooOutput } +@input structure FooInput { doc: InlineDocument, } +@output +structure FooOutput {} + document InlineDocument @yesDocuments diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-replace-trait.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-replace-trait.json index be85d5644b1..3dc6fd69090 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-replace-trait.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-replace-trait.json @@ -26,6 +26,20 @@ "type": "operation", "traits": { "smithy.api#idempotent": {} + }, + "input": {"target": "ns.foo#PutValidResourceInput"}, + "output": {"target": "ns.foo#PutValidResourceOutput"} + }, + "ns.foo#PutValidResourceInput": { + "type": "structure", + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#PutValidResourceOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} } }, "ns.foo#InvalidResource": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/ambiguous-name.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/ambiguous-name.errors new file mode 100644 index 00000000000..49d7aaabc86 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/ambiguous-name.errors @@ -0,0 +1,4 @@ +[WARNING] smithy.example#GetFooRequest: The name of this shape implies that it is the input of smithy.example#GetFoo, but that operation uses smithy.example#GetFoo_Input for input. This kind of ambiguity can confuse developers calling this operation and can cause issues in code generators that use similar naming conventions to generate input types. | OperationNameAmbiguity +[WARNING] smithy.example#GetFooResponse: The name of this shape implies that it is the output of smithy.example#GetFoo, but that operation uses smithy.example#GetFoo_Output for output. This kind of ambiguity can confuse developers calling this operation and can cause issues in code generators that use similar naming conventions to generate output types. | OperationNameAmbiguity +[WARNING] smithy.example#GetFooInput: The name of this shape implies that it is the input of smithy.example#GetFoo, but that operation uses smithy.example#GetFoo_Input for input. This kind of ambiguity can confuse developers calling this operation and can cause issues in code generators that use similar naming conventions to generate input types. | OperationNameAmbiguity +[WARNING] smithy.example#GetFooOutput: The name of this shape implies that it is the output of smithy.example#GetFoo, but that operation uses smithy.example#GetFoo_Output for output. This kind of ambiguity can confuse developers calling this operation and can cause issues in code generators that use similar naming conventions to generate output types. | OperationNameAmbiguity diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/ambiguous-name.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/ambiguous-name.smithy new file mode 100644 index 00000000000..7ee1d2cd0c9 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/ambiguous-name.smithy @@ -0,0 +1,24 @@ +// Finds operation names that imply they are the input/output of an +// operation, but the operation uses a different input/output shape. +$version: "1.0" + +namespace smithy.example + +operation GetFoo { + input: GetFoo_Input, + output: GetFoo_Output +} + +@input +structure GetFoo_Input {} + +@output +structure GetFoo_Output {} + +structure GetFooRequest {} + +structure GetFooResponse {} + +structure GetFooInput {} + +structure GetFooOutput {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-input-output-operation-bindings.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-input-output-operation-bindings.errors new file mode 100644 index 00000000000..5cb8bc67bae --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-input-output-operation-bindings.errors @@ -0,0 +1,4 @@ +[ERROR] smithy.example#GetFoo: Operation input cannot target structures marked with the @output trait | OperationInputOutputMisuse +[ERROR] smithy.example#GetFoo: Operation output cannot target structures marked with the @input trait | OperationInputOutputMisuse +[WARNING] smithy.example#GetFooInput: This structure is the output of `smithy.example#GetFoo`, but it is not marked with the @output trait. | OperationMissingOutputTrait +[WARNING] smithy.example#GetFooOutput: This structure is the input of `smithy.example#GetFoo`, but it is not marked with the @input trait. The @input trait gives operations more flexibility to evolve their top-level input members in ways that would otherwise be backward incompatible. | OperationMissingInputTrait diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-input-output-operation-bindings.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-input-output-operation-bindings.smithy new file mode 100644 index 00000000000..3ef5fb60472 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-input-output-operation-bindings.smithy @@ -0,0 +1,15 @@ +$version: "1.0" +namespace smithy.example + +operation GetFoo { + input: GetFooOutput, + output: GetFooInput // these are flipped! +} + +@input +@suppress(["OperationNameAmbiguity"]) +structure GetFooInput {} + +@output +@suppress(["OperationNameAmbiguity"]) +structure GetFooOutput {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-member-target-to-input-output.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-member-target-to-input-output.errors new file mode 100644 index 00000000000..d1f5880f70d --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-member-target-to-input-output.errors @@ -0,0 +1,2 @@ +[ERROR] smithy.example#BadStructure$i: Members cannot target structures marked with the @input trait: smithy.example#GetFooInput | OperationInputOutputMisuse +[ERROR] smithy.example#BadStructure$o: Members cannot target structures marked with the @output trait: smithy.example#GetFooOutput | OperationInputOutputMisuse diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-member-target-to-input-output.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-member-target-to-input-output.smithy new file mode 100644 index 00000000000..8c117edea3a --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-member-target-to-input-output.smithy @@ -0,0 +1,13 @@ +$version: "1.0" +namespace smithy.example + +@input +structure GetFooInput {} + +@output +structure GetFooOutput {} + +structure BadStructure { + i: GetFooInput, + o: GetFooOutput +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-multiple-operations-same-shape.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-multiple-operations-same-shape.errors new file mode 100644 index 00000000000..b34f96ffa50 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-multiple-operations-same-shape.errors @@ -0,0 +1,4 @@ +[WARNING] smithy.example#GetFoo2: The input of this operation should target a shape that starts with the operation's name, 'GetFoo2', but the targeted shape is `smithy.example#GetFooInput` | OperationInputOutputName +[WARNING] smithy.example#GetFoo2: The output of this operation should target a shape that starts with the operation's name, 'GetFoo2', but the targeted shape is `smithy.example#GetFooOutput` | OperationInputOutputName +[ERROR] smithy.example#GetFooInput: Shapes marked with the @input trait cannot be used as input by multiple operations: `smithy.example#GetFoo`, `smithy.example#GetFoo2` | OperationInputOutputMisuse +[ERROR] smithy.example#GetFooOutput: Shapes marked with the @output trait cannot be used as output by multiple operations: `smithy.example#GetFoo`, `smithy.example#GetFoo2` | OperationInputOutputMisuse diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-multiple-operations-same-shape.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-multiple-operations-same-shape.smithy new file mode 100644 index 00000000000..d8b5a8ce230 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/invalid-multiple-operations-same-shape.smithy @@ -0,0 +1,18 @@ +$version: "1.0" +namespace smithy.example + +operation GetFoo { + input: GetFooInput, + output: GetFooOutput +} + +operation GetFoo2 { + input: GetFooInput, + output: GetFooOutput +} + +@input +structure GetFooInput {} + +@output +structure GetFooOutput {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/valid-input-output-traits.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/valid-input-output-traits.errors new file mode 100644 index 00000000000..e69de29bb2d diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/valid-input-output-traits.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/valid-input-output-traits.smithy new file mode 100644 index 00000000000..4402c0e771e --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/operation/input-output-traits/valid-input-output-traits.smithy @@ -0,0 +1,13 @@ +$version: "1.0" +namespace smithy.example + +operation GetFoo { + input: GetFooInput, + output: GetFooOutput +} + +@input +structure GetFooInput {} + +@output +structure GetFooOutput {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-deeply-nested.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-deeply-nested.errors index 1ff66958b89..4e034982a89 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-deeply-nested.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-deeply-nested.errors @@ -1,2 +1,2 @@ -[WARNING] ns.foo#DeeplyNestedOutputOperation: paginated trait `items` contains a path with more than two parts, which can make your API cumbersome to use | PaginatedTrait -[WARNING] ns.foo#DeeplyNestedOutputOperation: paginated trait `outputToken` contains a path with more than two parts, which can make your API cumbersome to use | PaginatedTrait +[WARNING] ns.foo#DeeplyNested: paginated trait `items` contains a path with more than two parts, which can make your API cumbersome to use | PaginatedTrait +[WARNING] ns.foo#DeeplyNested: paginated trait `outputToken` contains a path with more than two parts, which can make your API cumbersome to use | PaginatedTrait diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-deeply-nested.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-deeply-nested.json index 4168fc97cb8..fb86ea4a1aa 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-deeply-nested.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-deeply-nested.json @@ -1,19 +1,10 @@ { "smithy": "1.0", "shapes": { - "ns.foo#Service": { - "type": "service", - "version": "2019-06-27", - "operations": [ - { - "target": "ns.foo#DeeplyNestedOutputOperation" - } - ] - }, - "ns.foo#DeeplyNestedOutputOperation": { + "ns.foo#DeeplyNested": { "type": "operation", "input": { - "target": "ns.foo#ValidInput" + "target": "ns.foo#DeeplyNestedInput" }, "output": { "target": "ns.foo#DeeplyNestedOutput" @@ -27,7 +18,7 @@ } } }, - "ns.foo#ValidInput": { + "ns.foo#DeeplyNestedInput": { "type": "structure", "members": { "nextToken": { @@ -36,25 +27,31 @@ "pageSize": { "target": "smithy.api#Integer" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#DeeplyNestedOutput": { "type": "structure", "members": { "output": { - "target": "ns.foo#ValidWrappedOutput" + "target": "ns.foo#WrappedOutput" } + }, + "traits": { + "smithy.api#output": {} } }, - "ns.foo#ValidWrappedOutput": { + "ns.foo#WrappedOutput": { "type": "structure", "members": { "result": { - "target": "ns.foo#ValidOutput" + "target": "ns.foo#InnerOutput" } } }, - "ns.foo#ValidOutput": { + "ns.foo#InnerOutput": { "type": "structure", "members": { "nextToken": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-invalid.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-invalid.errors index df47c198d7b..3a9ce8f2a3f 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-invalid.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-invalid.errors @@ -1,4 +1,3 @@ -[ERROR] ns.foo#Invalid1: paginated operations require an input | PaginatedTrait [ERROR] ns.foo#Invalid2: paginated trait `inputToken` targets a member `nextToken` that does not exist | PaginatedTrait [ERROR] ns.foo#Invalid2: paginated trait `outputToken` targets a member `nextToken` that does not exist | PaginatedTrait [ERROR] ns.foo#Invalid3: paginated trait `inputToken` member `InputNotString` targets a integer shape, but must target one of the following: [`map`, `string`] | PaginatedTrait @@ -8,11 +7,14 @@ [ERROR] ns.foo#Invalid4: paginated trait `outputToken` member `nextToken` must not be required | PaginatedTrait [WARNING] ns.foo#Invalid4: paginated trait `pageSize` member `pageSize` should not be required | PaginatedTrait [ERROR] ns.foo#Invalid5: paginated trait `pageSize` member `Invalid5Input` targets a string shape, but must target one of the following: [`integer`] | PaginatedTrait -[ERROR] ns.foo#Invalid6: paginated trait `items` member `Invalid6Input` targets a string shape, but must target one of the following: [`list`, `map`] | PaginatedTrait +[ERROR] ns.foo#Invalid6: paginated trait `items` member `Invalid6Output` targets a string shape, but must target one of the following: [`list`, `map`] | PaginatedTrait [ERROR] ns.foo#Invalid7: paginated trait `items` targets a member `items` that does not exist | PaginatedTrait [ERROR] ns.foo#Invalid7: paginated trait `outputToken` targets a member `missing` that does not exist | PaginatedTrait -[ERROR] ns.foo#InvalidNoOutput: paginated operations require an output | PaginatedTrait [ERROR] ns.foo#Invalid8: When bound within the `ns.foo#Service` service, paginated trait `inputToken` is not configured | PaginatedTrait [ERROR] ns.foo#Invalid8: When bound within the `ns.foo#Service` service, paginated trait `outputToken` is not configured | PaginatedTrait [ERROR] ns.foo#InvalidNestedInput: paginated trait `inputToken` does not allow path values | PaginatedTrait [ERROR] ns.foo#InvalidNestedInput: paginated trait `pageSize` does not allow path values | PaginatedTrait +[ERROR] ns.foo#Invalid1: paginated trait `inputToken` targets a member `nextToken` that does not exist | PaginatedTrait +[ERROR] ns.foo#Invalid1: paginated trait `pageSize` targets a member `pageSize` that does not exist | PaginatedTrait +[ERROR] ns.foo#InvalidNoOutput: paginated trait `items` targets a member `items` that does not exist | PaginatedTrait +[ERROR] ns.foo#InvalidNoOutput: paginated trait `outputToken` targets a member `nextToken` that does not exist | PaginatedTrait diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-invalid.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-invalid.json index 9904c042334..85f7e5a0af4 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-invalid.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-invalid.json @@ -1,5 +1,13 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"}, + {"id": "OperationMissingInputTrait", "namespace": "*"}, + {"id": "OperationMissingOutputTrait", "namespace": "*"} + ] + }, "shapes": { "ns.foo#Service": { "type": "service", @@ -222,7 +230,7 @@ "target": "ns.foo#ValidInput" }, "output": { - "target": "ns.foo#Invalid6Input" + "target": "ns.foo#Invalid6Output" }, "traits": { "smithy.api#readonly": {}, @@ -234,7 +242,7 @@ } } }, - "ns.foo#Invalid6Input": { + "ns.foo#Invalid6Output": { "type": "structure", "members": { "nextToken": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-map-tokens.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-map-tokens.errors index afa10e5b2d7..4a47be1d7c5 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-map-tokens.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-map-tokens.errors @@ -1,2 +1,2 @@ -[DANGER] ns.foo#MapTokens: paginated trait `inputToken` member `MapTokenInputOutput` targets a map shape, but this is not recommended. One of [`string`] SHOULD be targeted. | PaginatedTrait -[DANGER] ns.foo#MapTokens: paginated trait `outputToken` member `MapTokenInputOutput` targets a map shape, but this is not recommended. One of [`string`] SHOULD be targeted. | PaginatedTrait +[DANGER] ns.foo#MapTokens: paginated trait `inputToken` member `MapTokensInput` targets a map shape, but this is not recommended. One of [`string`] SHOULD be targeted. | PaginatedTrait +[DANGER] ns.foo#MapTokens: paginated trait `outputToken` member `MapTokensOutput` targets a map shape, but this is not recommended. One of [`string`] SHOULD be targeted. | PaginatedTrait diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-map-tokens.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-map-tokens.json index 77dd46f57fc..5cf9b2933c6 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-map-tokens.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-map-tokens.json @@ -13,10 +13,10 @@ "ns.foo#MapTokens": { "type": "operation", "input": { - "target": "ns.foo#MapTokenInputOutput" + "target": "ns.foo#MapTokensInput" }, "output": { - "target": "ns.foo#MapTokenInputOutput" + "target": "ns.foo#MapTokensOutput" }, "traits": { "smithy.api#readonly": {}, @@ -26,12 +26,26 @@ } } }, - "ns.foo#MapTokenInputOutput": { + "ns.foo#MapTokensInput": { "type": "structure", "members": { "nextToken": { "target": "ns.foo#StringMap" } + }, + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#MapTokensOutput": { + "type": "structure", + "members": { + "nextToken": { + "target": "ns.foo#StringMap" + } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#StringMap": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-targets.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-targets.json index 253e5b583fb..b94e27ac71c 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-targets.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-targets.json @@ -37,6 +37,9 @@ "pageSize": { "target": "ns.foo#Missing" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#UnresolvedOutput": { @@ -51,6 +54,9 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#output": {} } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-valid-merge.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-valid-merge.json index 3e735f43c7a..ed0ef12b4c8 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-valid-merge.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-valid-merge.json @@ -52,6 +52,9 @@ "pageSize": { "target": "smithy.api#Integer" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#ListResourcesOutput": { @@ -63,6 +66,9 @@ "items": { "target": "ns.foo#StringList" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#StringList": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-valid.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-valid.json index 2619e398838..b18a438fb09 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-valid.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/paginated/paginated-valid.json @@ -1,5 +1,13 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"}, + {"id": "OperationMissingInputTrait", "namespace": "*"}, + {"id": "OperationMissingOutputTrait", "namespace": "*"} + ] + }, "shapes": { "ns.foo#Service": { "type": "service", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/private-access.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/private-access.errors index a37c8cb517b..93760752a55 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/private-access.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/private-access.errors @@ -2,7 +2,7 @@ [ERROR] smithy.example#InvalidMap$key: This shape has an invalid member_target relationship that targets a private shape, `smithy.private#PrivateString`, in another namespace. | PrivateAccess [ERROR] smithy.example#InvalidMap$value: This shape has an invalid member_target relationship that targets a private shape, `smithy.private#PrivateString`, in another namespace. | PrivateAccess [ERROR] smithy.example#InvalidOperation: This shape has an invalid error relationship that targets a private shape, `smithy.private#PrivateError`, in another namespace. | PrivateAccess -[ERROR] smithy.example#InvalidOperation: This shape has an invalid input relationship that targets a private shape, `smithy.private#PrivateStructure`, in another namespace. | PrivateAccess -[ERROR] smithy.example#InvalidOperation: This shape has an invalid output relationship that targets a private shape, `smithy.private#PrivateStructure`, in another namespace. | PrivateAccess +[ERROR] smithy.example#InvalidOperation: This shape has an invalid input relationship that targets a private shape, `smithy.private#InvalidOperationInput`, in another namespace. | PrivateAccess +[ERROR] smithy.example#InvalidOperation: This shape has an invalid output relationship that targets a private shape, `smithy.private#InvalidOperationOutput`, in another namespace. | PrivateAccess [ERROR] smithy.example#InvalidService: This shape has an invalid operation relationship that targets a private shape, `smithy.private#PrivateOperation`, in another namespace. | PrivateAccess [ERROR] smithy.example#InvalidStructure$invalid: This shape has an invalid member_target relationship that targets a private shape, `smithy.private#PrivateString`, in another namespace. | PrivateAccess diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/private-access.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/private-access.json index 9d543b0294c..d3212a236e7 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/private-access.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/private-access.json @@ -39,10 +39,10 @@ "smithy.example#InvalidOperation": { "type": "operation", "input": { - "target": "smithy.private#PrivateStructure" + "target": "smithy.private#InvalidOperationInput" }, "output": { - "target": "smithy.private#PrivateStructure" + "target": "smithy.private#InvalidOperationOutput" }, "errors": [ { @@ -50,7 +50,7 @@ } ] }, - "smithy.private#PrivateStructure": { + "smithy.private#InvalidOperationInput": { "type": "structure", "members": { "valid": { @@ -67,7 +67,29 @@ } }, "traits": { - "smithy.api#private": {} + "smithy.api#private": {}, + "smithy.api#input": {} + } + }, + "smithy.private#InvalidOperationOutput": { + "type": "structure", + "members": { + "valid": { + "target": "smithy.private#PrivateString" + }, + "list": { + "target": "smithy.example#InvalidList" + }, + "struct": { + "target": "smithy.example#InvalidStructure" + }, + "map": { + "target": "smithy.example#InvalidMap" + } + }, + "traits": { + "smithy.api#private": {}, + "smithy.api#output": {} } }, "smithy.private#PrivateError": { @@ -86,10 +108,10 @@ "smithy.private#PrivateOperation": { "type": "operation", "input": { - "target": "smithy.private#PrivateStructure" + "target": "smithy.private#PrivateOperationInput" }, "output": { - "target": "smithy.private#PrivateStructure" + "target": "smithy.private#PrivateOperationOutput" }, "errors": [ { @@ -99,6 +121,48 @@ "traits": { "smithy.api#private": {} } + }, + "smithy.private#PrivateOperationInput": { + "type": "structure", + "members": { + "valid": { + "target": "smithy.private#PrivateString" + }, + "list": { + "target": "smithy.example#InvalidList" + }, + "struct": { + "target": "smithy.example#InvalidStructure" + }, + "map": { + "target": "smithy.example#InvalidMap" + } + }, + "traits": { + "smithy.api#private": {}, + "smithy.api#input": {} + } + }, + "smithy.private#PrivateOperationOutput": { + "type": "structure", + "members": { + "valid": { + "target": "smithy.private#PrivateString" + }, + "list": { + "target": "smithy.example#InvalidList" + }, + "struct": { + "target": "smithy.example#InvalidStructure" + }, + "map": { + "target": "smithy.example#InvalidMap" + } + }, + "traits": { + "smithy.api#private": {}, + "smithy.api#output": {} + } } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/request-token.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/request-token.errors index 25d36a6579b..34fb908697a 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/request-token.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/request-token.errors @@ -1,2 +1 @@ -[ERROR] ns.foo#InvalidInputWithMultiple: The `idempotencyToken` trait can be applied to only a single member of a structure, but it was found on the following members: `anotherToken`, `token` | ExclusiveStructureMemberTrait - +[ERROR] ns.foo#InvalidInput: The `idempotencyToken` trait can be applied to only a single member of a structure, but it was found on the following members: `anotherToken`, `token` | ExclusiveStructureMemberTrait diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/request-token.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/request-token.json index 7e2568f32ba..ccb3b6545cb 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/request-token.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/request-token.json @@ -5,6 +5,9 @@ "type": "operation", "input": { "target": "ns.foo#ValidInput" + }, + "output": { + "target": "ns.foo#ValidOutput" } }, "ns.foo#ValidInput": { @@ -16,6 +19,15 @@ "smithy.api#idempotencyToken": {} } } + }, + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#ValidOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} } }, "ns.foo#String": { @@ -24,13 +36,16 @@ "ns.foo#Invalid": { "type": "operation", "input": { - "target": "ns.foo#InvalidInputWithMultiple" + "target": "ns.foo#InvalidInput" + }, + "output": { + "target": "ns.foo#InvalidOutput" }, "traits": { "smithy.api#readonly": {} } }, - "ns.foo#InvalidInputWithMultiple": { + "ns.foo#InvalidInput": { "type": "structure", "members": { "token": { @@ -45,6 +60,15 @@ "smithy.api#idempotencyToken": {} } } + }, + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#InvalidOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/required-trait-sanity-check.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/required-trait-sanity-check.errors index 1fe5d56ecb2..462c099da35 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/required-trait-sanity-check.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/required-trait-sanity-check.errors @@ -1,3 +1,3 @@ -[ERROR] ns.foo#Blob: Trait `required` cannot be applied to `ns.foo#Blob`. This trait may only be applied to shapes that match the following selector: structure > member | TraitTarget [ERROR] ns.foo#Operation: Trait `required` cannot be applied to `ns.foo#Operation`. This trait may only be applied to shapes that match the following selector: structure > member | TraitTarget -[ERROR] ns.foo#Structure: Trait `required` cannot be applied to `ns.foo#Structure`. This trait may only be applied to shapes that match the following selector: structure > member | TraitTarget +[ERROR] ns.foo#OperationInput: Trait `required` cannot be applied to `ns.foo#OperationInput`. This trait may only be applied to shapes that match the following selector: structure > member | TraitTarget +[ERROR] ns.foo#Blob: Trait `required` cannot be applied to `ns.foo#Blob`. This trait may only be applied to shapes that match the following selector: structure > member | TraitTarget diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/required-trait-sanity-check.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/required-trait-sanity-check.json index aa71df9da3e..8810c796b4a 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/required-trait-sanity-check.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/required-trait-sanity-check.json @@ -3,11 +3,17 @@ "shapes": { "ns.foo#Operation": { "type": "operation", + "input": { + "target": "ns.foo#OperationInput" + }, + "output": { + "target": "ns.foo#OperationOutput" + }, "traits": { "smithy.api#required": {} } }, - "ns.foo#Structure": { + "ns.foo#OperationInput": { "type": "structure", "members": { "a": { @@ -18,9 +24,16 @@ } }, "traits": { + "smithy.api#input": {}, "smithy.api#required": {} } }, + "ns.foo#OperationOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} + } + }, "ns.foo#Blob": { "type": "blob", "traits": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/resource-must-bind-parent-identifiers.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/resource-must-bind-parent-identifiers.json index 1a4ce0e09bd..5d4eb8c5f33 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/resource-must-bind-parent-identifiers.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/resource-must-bind-parent-identifiers.json @@ -34,10 +34,22 @@ "type": "operation", "input": { "target": "ns.foo#InvokeBInput" + }, + "output": { + "target": "ns.foo#InvokeBOutput" } }, "ns.foo#InvokeBInput": { - "type": "structure" + "type": "structure", + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#InvokeBOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} + } } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/resource-operation-binding-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/resource-operation-binding-validator.json index e60fdd3d6e7..0177b05ca7e 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/resource-operation-binding-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/resource-operation-binding-validator.json @@ -33,6 +33,9 @@ "type": "operation", "input": { "target": "ns.foo#ValidResourceOperation1Input" + }, + "output": { + "target": "ns.foo#ValidResourceOperation1Output" } }, "ns.foo#ValidResourceOperation1Input": { @@ -44,17 +47,38 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#ValidResourceOperation1Output": { + "type": "structure", + "traits": { + "smithy.api#output": {} } }, "ns.foo#ValidResourceOperation2": { "type": "operation", "input": { "target": "ns.foo#ValidResourceOperation2Input" + }, + "output": { + "target": "ns.foo#ValidResourceOperation2Output" } }, "ns.foo#ValidResourceOperation2Input": { "type": "structure", - "members": {} + "members": {}, + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#ValidResourceOperation2Output": { + "type": "structure", + "traits": { + "smithy.api#output": {} + } }, "ns.foo#InvalidResource": { "type": "resource", @@ -76,16 +100,30 @@ "type": "operation", "input": { "target": "ns.foo#InvalidResourceOperation1Input" + }, + "output": { + "target": "ns.foo#InvalidResourceOperation1Output" } }, "ns.foo#InvalidResourceOperation1Input": { "type": "structure", - "members": {} + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#InvalidResourceOperation1Output": { + "type": "structure", + "traits": { + "smithy.api#output": {} + } }, "ns.foo#InvalidResourceOperation2": { "type": "operation", "input": { "target": "ns.foo#InvalidResourceOperation2Input" + }, + "output": { + "target": "ns.foo#InvalidResourceOperation2Output" } }, "ns.foo#InvalidResourceOperation2Input": { @@ -97,6 +135,15 @@ "smithy.api#required": {} } } + }, + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#InvalidResourceOperation2Output": { + "type": "structure", + "traits": { + "smithy.api#output": {} } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/sensitive-trait.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/sensitive-trait.json index bff6ff5d9b9..cb4cb2eac6a 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/sensitive-trait.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/sensitive-trait.json @@ -2,11 +2,29 @@ "smithy": "1.0", "shapes": { "ns.foo#Operation": { - "type": "operation", + "type": "operation", + "input": { + "target": "ns.foo#OperationInput" + }, + "output": { + "target": "ns.foo#OperationOutput" + }, "traits": { "smithy.api#sensitive": {} } - }, + }, + "ns.foo#OperationInput": { + "type": "structure", + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#OperationOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} + } + }, "ns.foo#Structure": { "type": "structure", "members": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-conflict-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-conflict-validator.json index a0ec1abd405..23de44b4a41 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-conflict-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-conflict-validator.json @@ -1,5 +1,11 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "*"}, + {"id": "OperationMissingOutput", "namespace": "*"} + ] + }, "shapes": { "ns.foo#MyService": { "type": "service", @@ -66,6 +72,9 @@ "structureConflict2": {"target": "another.ns#Baz"}, "unionConflict1": {"target": "ns.foo#Qux"}, "unionConflict2": {"target": "another.ns#Qux"} + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#ListA": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/conflict.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/conflict.smithy index d7d586ba8ea..2da4c7fc551 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/conflict.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/conflict.smithy @@ -13,13 +13,18 @@ service MyService { operation SayHello { input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { foo: Foo, baz: Baz, } +@output +structure SayHelloOutput {} + structure Foo {} structure Baz {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/error-rename-invalid.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/error-rename-invalid.smithy index f634696d9ab..48eaf617ec1 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/error-rename-invalid.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/error-rename-invalid.smithy @@ -11,8 +11,16 @@ service MyService { } operation SayHello { + input: SayHelloInput, + output: SayHelloOutput, errors: [BadGreeting] } +@input +structure SayHelloInput {} + +@output +structure SayHelloOutput {} + @error("client") structure BadGreeting {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/invalid-identifier.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/invalid-identifier.smithy index 366a9ee80c2..65761f32ff2 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/invalid-identifier.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/invalid-identifier.smithy @@ -12,10 +12,15 @@ service MyService { operation SayHello { input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { foo: Foo } +@output +structure SayHelloOutput {} + structure Foo {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/member-invalid.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/member-invalid.smithy index 5c40494810e..755026ef2e3 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/member-invalid.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/member-invalid.smithy @@ -12,8 +12,13 @@ service MyService { operation SayHello { input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { foo: String, } + +@output +structure SayHelloOutput {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/operation-invalid.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/operation-invalid.smithy index de0a4f9db12..7072d8f84b8 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/operation-invalid.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/operation-invalid.smithy @@ -10,4 +10,13 @@ service MyService { } } -operation SayHello {} +operation SayHello { + input: SayHelloInput, + output: SayHelloOutput +} + +@input +structure SayHelloInput {} + +@output +structure SayHelloOutput {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/redundant-rename-invalid.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/redundant-rename-invalid.smithy index 1b48b44e9e5..9740472f364 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/redundant-rename-invalid.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/redundant-rename-invalid.smithy @@ -12,6 +12,11 @@ service MyService { operation SayHello { input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput {} + +@output +structure SayHelloOutput {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/shape-same-name.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/shape-same-name.smithy index f04cf6be2ed..a0e6aaadd53 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/shape-same-name.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-rename/shape-same-name.smithy @@ -13,13 +13,18 @@ service MyService { operation SayHello { input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { foo: Foo, - baz: Baz, + baz: Baz } +@output +structure SayHelloOutput {} + structure Foo {} structure Baz {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/single-operation-binding.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/single-operation-binding.json index 38286991352..60056f52ff8 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/single-operation-binding.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/single-operation-binding.json @@ -1,5 +1,11 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "ns.foo"}, + {"id": "OperationMissingOutput", "namespace": "ns.foo"} + ] + }, "shapes": { "ns.foo#MyService": { "type": "service", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait-event.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait-event.json index 914e8a4f0e5..451c4f1d406 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait-event.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait-event.json @@ -1,5 +1,13 @@ { "smithy": "1.0", + "metadata": { + "suppressions": [ + {"id": "OperationMissingInput", "namespace": "ns.foo"}, + {"id": "OperationMissingOutput", "namespace": "ns.foo"}, + {"id": "OperationMissingInputTrait", "namespace": "ns.foo"}, + {"id": "OperationMissingOutputTrait", "namespace": "ns.foo"} + ] + }, "shapes": { "ns.foo#ValidMultiEventInputOperation": { "type": "operation", diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait.errors index 7e8de4e3332..6c7ad2a8b4b 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait.errors @@ -1,2 +1,2 @@ -[ERROR] ns.foo#InvalidStreamingOutput: Only a single member of a structure can target a shape marked with the `streaming` trait, but it was found on the following members: `StreamingBlob1`, `StreamingBlob2` | ExclusiveStructureMemberTrait -[ERROR] ns.foo#InvalidNestedStream$NestedStream: This shape has an invalid `MEMBER_TARGET` relationship to a structure, `ns.foo#StreamingInput`, that contains a stream | StreamingTrait +[ERROR] ns.foo#InvalidStreamingOperationOutput: Only a single member of a structure can target a shape marked with the `streaming` trait, but it was found on the following members: `StreamingBlob1`, `StreamingBlob2` | ExclusiveStructureMemberTrait +[ERROR] ns.foo#InvalidNestedStream$NestedStream: This shape has an invalid `MEMBER_TARGET` relationship to a structure, `ns.foo#NestedStreamContainer`, that contains a stream | StreamingTrait diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait.json index 8e081b7250a..abd0dbde1d5 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-trait.json @@ -7,26 +7,32 @@ "ns.foo#StreamingOperation": { "type": "operation", "input": { - "target": "ns.foo#StreamingInput" + "target": "ns.foo#StreamingOperationInput" }, "output": { - "target": "ns.foo#StreamingOutput" + "target": "ns.foo#StreamingOperationOutput" } }, - "ns.foo#StreamingInput": { + "ns.foo#StreamingOperationInput": { "type": "structure", "members": { "Body": { "target": "ns.foo#StreamingBlob" } + }, + "traits": { + "smithy.api#input": {} } }, - "ns.foo#StreamingOutput": { + "ns.foo#StreamingOperationOutput": { "type": "structure", "members": { "Body": { "target": "ns.foo#StreamingBlob" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#StreamingBlob": { @@ -37,11 +43,20 @@ }, "ns.foo#InvalidStreamingOperation": { "type": "operation", + "input": { + "target": "ns.foo#InvalidStreamingOperationInput" + }, "output": { - "target": "ns.foo#InvalidStreamingOutput" + "target": "ns.foo#InvalidStreamingOperationOutput" + } + }, + "ns.foo#InvalidStreamingOperationInput": { + "type": "structure", + "traits": { + "smithy.api#input": {} } }, - "ns.foo#InvalidStreamingOutput": { + "ns.foo#InvalidStreamingOperationOutput": { "type": "structure", "members": { "StreamingBlob1": { @@ -50,13 +65,24 @@ "StreamingBlob2": { "target": "ns.foo#StreamingBlob" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#InvalidNestedStream": { "type": "structure", "members": { "NestedStream": { - "target": "ns.foo#StreamingInput" + "target": "ns.foo#NestedStreamContainer" + } + } + }, + "ns.foo#NestedStreamContainer": { + "type": "structure", + "members": { + "nested": { + "target": "ns.foo#StreamingBlob" } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/target-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/target-validator.json index a30717730e4..02fa9cf2727 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/target-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/target-validator.json @@ -144,6 +144,9 @@ "integer": { "target": "ns.foo#Integer" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#ValidOutput": { @@ -179,6 +182,9 @@ "another6": { "target": "ns.foo#InvalidTraitReference" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#ValidError": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/trait-target.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/trait-target.json index f3e5a342356..87eb1ab1345 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/trait-target.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/trait-target.json @@ -14,6 +14,9 @@ "type": "operation", "input": { "target": "ns.foo#InvokeInput" + }, + "output": { + "target": "ns.foo#InvokeOutput" } }, "ns.foo#InvokeInput": { @@ -25,6 +28,15 @@ "ns.foo#test": {} } } + }, + "traits": { + "smithy.api#input": {} + } + }, + "ns.foo#InvokeOutput": { + "type": "structure", + "traits": { + "smithy.api#output": {} } }, "ns.foo#Invalid": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/invalid-members.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/invalid-members.errors new file mode 100644 index 00000000000..38508fd0c98 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/invalid-members.errors @@ -0,0 +1,4 @@ +[ERROR] smithy.example#Foo$bar: Only members of a union can reference smithy.api#Unit | UnitType +[ERROR] smithy.example#Baz$member: Only members of a union can reference smithy.api#Unit | UnitType +[ERROR] smithy.example#Boo$member: Only members of a union can reference smithy.api#Unit | UnitType +[ERROR] smithy.example#Bam$value: Only members of a union can reference smithy.api#Unit | UnitType diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/invalid-members.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/invalid-members.smithy new file mode 100644 index 00000000000..281937c04a1 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/invalid-members.smithy @@ -0,0 +1,20 @@ +$version: "1.0" + +namespace smithy.example + +structure Foo { + bar: Unit +} + +list Baz { + member: Unit +} + +set Boo { + member: Unit +} + +map Bam { + key: String, + value: Unit +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-operation.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-operation.errors new file mode 100644 index 00000000000..e69de29bb2d diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-operation.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-operation.smithy new file mode 100644 index 00000000000..46fb41da150 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-operation.smithy @@ -0,0 +1,8 @@ +$version: "1.0" + +namespace smithy.example + +operation Example { + input: Unit, + output: Unit +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-union.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-union.errors new file mode 100644 index 00000000000..e69de29bb2d diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-union.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-union.smithy new file mode 100644 index 00000000000..a8650842638 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unit/valid-union.smithy @@ -0,0 +1,9 @@ +$version: "1.0" + +namespace smithy.example + +union Foo { + baz: Unit, + bar: String, + bam: Unit +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unreferenced-shape-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unreferenced-shape-validator.json index bd7a0343f15..8214db9665b 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unreferenced-shape-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/unreferenced-shape-validator.json @@ -33,6 +33,9 @@ "int": { "target": "ns.foo#ReferencedInteger" } + }, + "traits": { + "smithy.api#input": {} } }, "ns.foo#ReferencedOperationOutput": { @@ -41,6 +44,9 @@ "float": { "target": "ns.foo#ReferencedFloat" } + }, + "traits": { + "smithy.api#output": {} } }, "ns.foo#ReferencedInteger": { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-shapes.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-shapes.smithy index 49760b044ce..28ed21119fc 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-shapes.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-shapes.smithy @@ -46,7 +46,10 @@ resource SubResource { }, } -operation EmptyOperation {} +operation EmptyOperation { + input: Unit, + output: Unit, +} operation MyOperation { input: InputOutput, @@ -59,11 +62,13 @@ operation MyOperation { @readonly operation ReadonlyResourceOperation { input: ResourceOperationInput, + output: Unit, } @idempotent operation ResourceOperation { input: ResourceOperationInput, + output: Unit, } @error("client") diff --git a/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/ResolvedTopicIndex.java b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/ResolvedTopicIndex.java index 4c8342789b0..87c0b97f964 100644 --- a/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/ResolvedTopicIndex.java +++ b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/ResolvedTopicIndex.java @@ -68,7 +68,7 @@ public ResolvedTopicIndex(Model model) { createPublishBindings(operationIndex, operation, trait); } else if (operation.hasTrait(SubscribeTrait.class)) { SubscribeTrait trait = operation.getTrait(SubscribeTrait.class).get(); - StructureShape input = operationIndex.getInput(operation).orElse(null); + StructureShape input = operationIndex.expectInputShape(operation); createSubscribeBinding(input, eventStreamIndex, operation, trait); } }); @@ -139,12 +139,8 @@ private void createPublishBindings( OperationShape operation, PublishTrait trait ) { - TopicBinding topicBinding = operationIndex.getInput(operation) - // Use the input to create a publish binding. - .map(input -> new TopicBinding<>(operation, trait, trait.getTopic(), input, input)) - // The binding has no valid input. - .orElseGet(() -> new TopicBinding<>(operation, trait, trait.getTopic(), null, null)); - + StructureShape input = operationIndex.expectInputShape(operation); + TopicBinding topicBinding = new TopicBinding<>(operation, trait, trait.getTopic(), input, input); publishBindings.put(operation.getId(), topicBinding); } diff --git a/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/TopicBinding.java b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/TopicBinding.java index 2e5f8915981..ddbeb972c3c 100644 --- a/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/TopicBinding.java +++ b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/TopicBinding.java @@ -133,11 +133,22 @@ public Topic getTopic() { * Gets the input shape related to this operation. * * @return Returns the optional input shape. + * @deprecated Use getInputShape instead. */ + @Deprecated public Optional getInput() { return Optional.ofNullable(input); } + /** + * Gets the input shape related to this operation. + * + * @return Returns the optional input shape. + */ + public StructureShape getInputShape() { + return input; + } + /** * Gets the payload shape of the topic. * diff --git a/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttPublishInputValidator.java b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttPublishInputValidator.java index e583414d79c..b30387f1d1a 100644 --- a/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttPublishInputValidator.java +++ b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttPublishInputValidator.java @@ -15,17 +15,17 @@ package software.amazon.smithy.mqtt.traits.validators; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.OperationIndex; +import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; -import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.model.validation.AbstractValidator; import software.amazon.smithy.model.validation.ValidationEvent; import software.amazon.smithy.mqtt.traits.PublishTrait; -import software.amazon.smithy.utils.OptionalUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** @@ -35,19 +35,27 @@ public final class MqttPublishInputValidator extends AbstractValidator { @Override public List validate(Model model) { - return model.shapes(OperationShape.class) - .filter(shape -> shape.hasTrait(PublishTrait.class)) - .flatMap(shape -> validateOperation(model, shape)) - .collect(Collectors.toList()); + OperationIndex operationIndex = OperationIndex.of(model); + List events = new ArrayList<>(); + for (OperationShape shape : model.getOperationShapesWithTrait(PublishTrait.class)) { + validateOperation(model, shape, operationIndex.expectInputShape(shape), events); + } + return events; } - private Stream validateOperation(Model model, OperationShape operation) { - return OptionalUtils.stream(operation.getInput().flatMap(model::getShape).flatMap(Shape::asStructureShape)) - .flatMap(input -> input.getAllMembers().values().stream() - .filter(member -> StreamingTrait.isEventStream(model, member)) - .map(member -> error(member, String.format( - "The input of `smithy.mqtt#publish` operations cannot contain event streams, " - + "and this member is used as part of the input of the `%s` operation.", - operation.getId())))); + private void validateOperation( + Model model, + OperationShape operation, + StructureShape input, + List events + ) { + for (MemberShape member : input.getAllMembers().values()) { + if (StreamingTrait.isEventStream(model, member)) { + events.add(error(member, String.format( + "The input of `smithy.mqtt#publish` operations cannot contain event streams, " + + "and this member is used as part of the input of the `%s` operation.", + operation.getId()))); + } + } } } diff --git a/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttSubscribeInputValidator.java b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttSubscribeInputValidator.java index 4a04da5be89..530d0c8dfb2 100644 --- a/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttSubscribeInputValidator.java +++ b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttSubscribeInputValidator.java @@ -15,17 +15,17 @@ package software.amazon.smithy.mqtt.traits.validators; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.OperationIndex; +import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; -import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.validation.AbstractValidator; import software.amazon.smithy.model.validation.ValidationEvent; import software.amazon.smithy.mqtt.traits.SubscribeTrait; import software.amazon.smithy.mqtt.traits.TopicLabelTrait; -import software.amazon.smithy.utils.OptionalUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** @@ -39,19 +39,22 @@ public final class MqttSubscribeInputValidator extends AbstractValidator { @Override public List validate(Model model) { - return model.shapes(OperationShape.class) - .filter(shape -> shape.hasTrait(SubscribeTrait.class)) - .flatMap(shape -> validateOperation(model, shape)) - .collect(Collectors.toList()); + OperationIndex operationIndex = OperationIndex.of(model); + List events = new ArrayList<>(); + for (OperationShape shape : model.getOperationShapesWithTrait(SubscribeTrait.class)) { + validateOperation(shape, operationIndex.expectInputShape(shape), events); + } + return events; } - private Stream validateOperation(Model model, OperationShape operation) { - return OptionalUtils.stream(operation.getInput().flatMap(model::getShape).flatMap(Shape::asStructureShape)) - .flatMap(input -> input.getAllMembers().values().stream() - .filter(member -> !member.hasTrait(TopicLabelTrait.class)) - .map(member -> error(member, String.format( - "All input members of an operation marked with the `smithy.mqtt#subscribe` trait " - + "must be marked with the `smithy.mqtt#topicLabel` trait, and this member is used " - + "as part of the input of the `%s` operation.", operation.getId())))); + private void validateOperation(OperationShape operation, StructureShape input, List events) { + for (MemberShape member : input.getAllMembers().values()) { + if (!member.hasTrait(TopicLabelTrait.class)) { + events.add(error(member, String.format( + "All input members of an operation marked with the `smithy.mqtt#subscribe` trait " + + "must be marked with the `smithy.mqtt#topicLabel` trait, and this member is used " + + "as part of the input of the `%s` operation.", operation.getId()))); + } + } } } diff --git a/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttTopicLabelValidator.java b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttTopicLabelValidator.java index 7f39e6ca3e9..82cae61c58e 100644 --- a/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttTopicLabelValidator.java +++ b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttTopicLabelValidator.java @@ -18,13 +18,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; -import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.traits.Trait; import software.amazon.smithy.model.validation.AbstractValidator; @@ -50,20 +49,25 @@ public class MqttTopicLabelValidator extends AbstractValidator { @Override public List validate(Model model) { - return model.shapes(OperationShape.class) - .map(MqttTopicLabelValidator::createTopics) - .filter(Objects::nonNull) - .flatMap(topics -> validateMqtt(model, topics).stream()) - .collect(Collectors.toList()); + OperationIndex index = OperationIndex.of(model); + List events = new ArrayList<>(); + for (OperationShape operation : model.getOperationShapes()) { + TopicCollection topics = createTopics(operation); + if (topics != null) { + events.addAll(validateMqtt(index, topics)); + } + } + + return events; } private static TopicCollection createTopics(OperationShape shape) { if (shape.hasTrait(SubscribeTrait.class)) { - SubscribeTrait trait = shape.getTrait(SubscribeTrait.class).get(); + SubscribeTrait trait = shape.expectTrait(SubscribeTrait.class); List bindings = Collections.singletonList(trait.getTopic()); return new TopicCollection(shape, trait, bindings); } else if (shape.hasTrait(PublishTrait.class)) { - PublishTrait trait = shape.getTrait(PublishTrait.class).get(); + PublishTrait trait = shape.expectTrait(PublishTrait.class); List bindings = Collections.singletonList(trait.getTopic()); return new TopicCollection(shape, trait, bindings); } else { @@ -71,32 +75,17 @@ private static TopicCollection createTopics(OperationShape shape) { } } - private List validateMqtt(Model model, TopicCollection topics) { + private List validateMqtt(OperationIndex index, TopicCollection topics) { Set labels = topics.getLabels(); - StructureShape input = topics.operation.getInput() - .flatMap(model::getShape) - .flatMap(Shape::asStructureShape) - .orElse(null); - - if (!labels.isEmpty() && input == null) { - return Collections.singletonList(error(topics.operation, topics.trait, String.format( - "Operation MQTT trait, `%s`, contains topic labels, [%s], but the operation has no input", - Trait.getIdiomaticTraitName(topics.trait.toShapeId()), - ValidationUtils.tickedList(labels)))); - } - - if (input == null) { - // No labels, and no input. - return Collections.emptyList(); - } - + StructureShape input = index.expectInputShape(topics.operation); List events = new ArrayList<>(); + for (MemberShape member : input.getAllMembers().values()) { if (member.hasTrait(TopicLabelTrait.class)) { if (labels.contains(member.getMemberName())) { labels.remove(member.getMemberName()); } else { - events.add(error(member, member.getTrait(TopicLabelTrait.class).get(), String.format( + events.add(error(member, member.expectTrait(TopicLabelTrait.class), String.format( "This member is marked with the `smithy.mqtt#topicLabel` trait, but when this member is " + "used as part of the input of the `%s` operation, a corresponding label cannot be " + "found in the `%s` trait", diff --git a/smithy-mqtt-traits/src/main/resources/META-INF/smithy/smithy.api.mqtt.smithy b/smithy-mqtt-traits/src/main/resources/META-INF/smithy/smithy.api.mqtt.smithy index 5df02c36f21..091a9a05622 100644 --- a/smithy-mqtt-traits/src/main/resources/META-INF/smithy/smithy.api.mqtt.smithy +++ b/smithy-mqtt-traits/src/main/resources/META-INF/smithy/smithy.api.mqtt.smithy @@ -6,7 +6,7 @@ namespace smithy.mqtt @protocolDefinition structure mqttJson {} -@trait(selector: "operation:not(-[output]->)", +@trait(selector: "operation :not(-[output]-> * > member)", conflicts: ["smithy.mqtt#subscribe"]) @tags(["diff.error.const"]) // Matches one or more characters that are not "#" or "+". diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/dropped-mqtt-headers.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/dropped-mqtt-headers.smithy index 246840361f9..5748cb8bba1 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/dropped-mqtt-headers.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/dropped-mqtt-headers.smithy @@ -5,12 +5,11 @@ namespace smithy.example @smithy.mqtt#subscribe("events") operation Foo { - input: FooInput, + input: Unit, output: FooOutput } -structure FooInput {} - +@output structure FooOutput { messages: EventStream } diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/job-service.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/job-service.smithy index 0d675ee1825..755c98debfc 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/job-service.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/job-service.smithy @@ -1,3 +1,12 @@ +$version: "1.0" + +metadata suppressions = [ + {id: "OperationMissingInput", namespace: "aws.iotjobs"}, + {id: "OperationMissingInputTrait", namespace: "aws.iotjobs"}, + {id: "OperationMissingOutput", namespace: "aws.iotjobs"}, + {id: "OperationMissingOutputTrait", namespace: "aws.iotjobs"}, +] + namespace aws.iotjobs use smithy.mqtt#mqttJson diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-operations-with-errors.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-operations-with-errors.smithy index 97f8796592c..4dca22d6f59 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-operations-with-errors.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-operations-with-errors.smithy @@ -4,12 +4,14 @@ namespace smithy.example @smithy.mqtt#subscribe("event1") operation Foo { + input: Unit, output: FooOutput, errors: [Error] } +@output structure FooOutput { - messages: EventStream, + messages: EventStream, } @streaming @@ -26,7 +28,9 @@ structure Error {} @smithy.mqtt#publish("event2") operation Baz { input: BazInput, + output: Unit, errors: [Error] } +@input structure BazInput {} diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-topic-labels.errors b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-topic-labels.errors index b512a9ac502..22efbf6f51e 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-topic-labels.errors +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-topic-labels.errors @@ -1,4 +1,4 @@ -[ERROR] smithy.example#Operation1: Operation MQTT trait, `smithy.mqtt#publish`, contains topic labels, [`foo`], but the operation has no input | MqttTopicLabel +[ERROR] smithy.example#Operation1: The `smithy.mqtt#publish` trait contains the following topic labels that could not be found in the input structure of the operation or were not marked with the `smithy.mqtt#topicLabel` trait: [`foo`] | MqttTopicLabel [ERROR] smithy.example#Operation2: The `smithy.mqtt#publish` trait contains the following topic labels that could not be found in the input structure of the operation or were not marked with the `smithy.mqtt#topicLabel` trait: [`foo`] | MqttTopicLabel [ERROR] smithy.example#Operation3Input$baz: This member is marked with the `smithy.mqtt#topicLabel` trait, but when this member is used as part of the input of the `smithy.example#Operation3` operation, a corresponding label cannot be found in the `smithy.mqtt#publish` trait | MqttTopicLabel [ERROR] smithy.example#Operation4: The `smithy.mqtt#publish` trait contains the following topic labels that could not be found in the input structure of the operation or were not marked with the `smithy.mqtt#topicLabel` trait: [`foo`] | MqttTopicLabel diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-topic-labels.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-topic-labels.smithy index 4c3c6d790b1..dfc1f69314c 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-topic-labels.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/mqtt-topic-labels.smithy @@ -2,57 +2,63 @@ namespace smithy.example // Missing input for {foo} property. @smithy.mqtt#publish("events1/{foo}") +@suppress(["OperationMissingInput", "OperationMissingOutput"]) operation Operation1 {} // Missing {foo} member. @smithy.mqtt#publish("events2/{foo}") operation Operation2 { - input: Operation2Input + input: Operation2Input, + output: Unit } +@input structure Operation2Input { - baz: smithy.api#String, + baz: smithy.api#String, } - // Extraneous {baz} label member. @smithy.mqtt#publish("events3/{foo}") operation Operation3 { - input: Operation3Input + input: Operation3Input, + output: Unit } +@input structure Operation3Input { - @required - @smithy.mqtt#topicLabel - foo: smithy.api#String, + @required + @smithy.mqtt#topicLabel + foo: smithy.api#String, - @required - @smithy.mqtt#topicLabel - baz: smithy.api#String, + @required + @smithy.mqtt#topicLabel + baz: smithy.api#String, } - // Missing topicLabel trait for {foo} @smithy.mqtt#publish("events4/{foo}") operation Operation4 { - input: Operation4Input + input: Operation4Input, + output: Unit } +@input structure Operation4Input { - @required - foo: smithy.api#String, + @required + foo: smithy.api#String, } - // No errors. @smithy.mqtt#publish("events5/{foo}") +@suppress(["OperationMissingOutput"]) operation Operation5 { input: Operation5Input } +@input structure Operation5Input { - @required - @smithy.mqtt#topicLabel - foo: smithy.api#String, + @required + @smithy.mqtt#topicLabel + foo: smithy.api#String, } diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/publish-with-eventstream.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/publish-with-eventstream.smithy index 7de2ac3a84a..05d432936eb 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/publish-with-eventstream.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/publish-with-eventstream.smithy @@ -2,11 +2,13 @@ namespace smithy.example @smithy.mqtt#publish("foo") operation Publish { - input: PublishInput + input: PublishInput, + output: Unit } +@input structure PublishInput { - messages: EventStream, + messages: EventStream, } @streaming diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-input-missing-label.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-input-missing-label.smithy index b0b4b679a7b..6b272c28da7 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-input-missing-label.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-input-missing-label.smithy @@ -13,16 +13,18 @@ operation Foo { output: FooOutput } +@input structure FooInput { - @required - @topicLabel - foo: smithy.api#String, + @required + @topicLabel + foo: smithy.api#String, - baz: smithy.api#String, // Error, missing topicLabel. + baz: smithy.api#String, // Error, missing topicLabel. } +@output structure FooOutput { - messages: EventStream, + messages: EventStream, } @streaming diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-missing-output.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-missing-output.smithy index 629b5e8aaac..73281534bcd 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-missing-output.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-missing-output.smithy @@ -1,6 +1,7 @@ -// Subscribe operations must define output structures. +// Subscribe operations must define output structures that contain an event stream. namespace smithy.example @smithy.mqtt#subscribe("events") +@suppress(["OperationMissingInput", "OperationMissingOutput"]) operation Foo {} diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-operation-initial-event.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-operation-initial-event.smithy index 34a28cf8998..4897cffb6cd 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-operation-initial-event.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-operation-initial-event.smithy @@ -4,12 +4,14 @@ namespace smithy.example @smithy.mqtt#subscribe("events") operation Foo { + input: Unit, output: FooOutput } +@output structure FooOutput { - badMember: smithy.api#String, // <-- Erroneous initial event member - messages: EventStream, + badMember: smithy.api#String, // <-- Erroneous initial event member + messages: EventStream, } @streaming diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-operation-missing-event-stream.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-operation-missing-event-stream.smithy index 0263f26196c..baa468a417d 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-operation-missing-event-stream.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/subscribe-operation-missing-event-stream.smithy @@ -4,7 +4,9 @@ namespace smithy.example @smithy.mqtt#subscribe("events") operation Foo { + input: Unit, output: FooOutput } +@output structure FooOutput {} diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/topic-conflicts.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/topic-conflicts.smithy index cbdeb3d5561..ba7d79c4048 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/topic-conflicts.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/errorfiles/topic-conflicts.smithy @@ -1,9 +1,17 @@ +$version: "1.0" + +metadata suppressions = [ + {id: "OperationMissingInputTrait", namespace: "smithy.example"}, + {id: "OperationMissingOutputTrait", namespace: "smithy.example"} +] + namespace smithy.example // Conflicts with B, C @smithy.mqtt#publish("a") operation A { - input: AInput + input: AInput, + output: Unit } structure AInput {} @@ -11,7 +19,8 @@ structure AInput {} // Conflicts with A, C @smithy.mqtt#publish("a") operation B { - input: BInput + input: BInput, + output: Unit } structure BInput {} @@ -19,6 +28,7 @@ structure BInput {} // Conflicts with A, B @smithy.mqtt#subscribe("a") operation C { + input: Unit, output: COutput } @@ -37,22 +47,26 @@ structure EmptyEvent {} // D and E do not conflict since they use the same payload. @smithy.mqtt#publish("b") operation D { - input: DInput + input: DInput, + output: Unit } + structure DInput {} @smithy.mqtt#publish("b") operation E { - input: DInput + input: DInput, + output: Unit } @smithy.mqtt#subscribe("b") operation F { + input: Unit, output: FOutput } structure FOutput { - messages: DInputEventStream, + messages: DInputEventStream, } @streaming @@ -61,6 +75,6 @@ union DInputEventStream { } structure DInputEvent { - @eventPayload - payload: DInput, + @eventPayload + payload: DInput, } diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java index 1dd4c7e1905..c713b84e172 100644 --- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java +++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java @@ -618,19 +618,7 @@ private OperationObject updateResponses( OpenApiMapper plugin ) { Map newResponses = new LinkedHashMap<>(); - - // OpenAPI requires at least one response, so track the "original" - // responses vs new/mutated responses. - Map originalResponses = operation.getResponses(); - if (operation.getResponses().isEmpty()) { - String code = context.getOpenApiProtocol().getOperationResponseStatusCode(context, shape); - String contextName = context.getService().getContextualName(shape); - originalResponses = MapUtils.of(code, ResponseObject.builder() - .description(contextName + " response") - .build()); - } - - for (Map.Entry entry : originalResponses.entrySet()) { + for (Map.Entry entry : operation.getResponses().entrySet()) { String status = entry.getKey(); ResponseObject responseObject = plugin.updateResponse( context, shape, status, methodName, path, entry.getValue()); diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java index ec718f401e5..5bf617f797b 100644 --- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java +++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java @@ -132,11 +132,7 @@ public Set getProtocolRequestHeaders(Context context, OperationShape public Set getProtocolResponseHeaders(Context context, OperationShape operationShape) { Set headers = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); headers.addAll(OpenApiProtocol.super.getProtocolResponseHeaders(context, operationShape)); - - // If the operation has any defined output or errors, it can return content-type. - if (operationShape.getOutput().isPresent() || !operationShape.getErrors().isEmpty()) { - headers.addAll(ProtocolUtils.CONTENT_HEADERS); - } + headers.addAll(ProtocolUtils.CONTENT_HEADERS); return headers; } @@ -419,10 +415,9 @@ private Map createResponses( Map result = new TreeMap<>(); OperationIndex operationIndex = OperationIndex.of(context.getModel()); - operationIndex.getOutput(operation).ifPresent(output -> { - updateResponsesMapWithResponseStatusAndObject( - context, bindingIndex, eventStreamIndex, operation, output, result); - }); + StructureShape output = operationIndex.expectOutputShape(operation); + updateResponsesMapWithResponseStatusAndObject( + context, bindingIndex, eventStreamIndex, operation, output, result); for (StructureShape error : operationIndex.getErrors(operation)) { updateResponsesMapWithResponseStatusAndObject( diff --git a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1ProtocolTest.java b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1ProtocolTest.java index 9f4ae81af28..c76aba040d0 100644 --- a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1ProtocolTest.java +++ b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/protocols/AwsRestJson1ProtocolTest.java @@ -143,7 +143,9 @@ private static Stream protocolHeaderCases() { ), SetUtils.of( "X-Amzn-Requestid", - "X-Amzn-Errortype" + "X-Amzn-Errortype", + "Content-Length", + "Content-Type" ) ), Arguments.of( @@ -187,7 +189,9 @@ private static Stream protocolHeaderCases() { ), SetUtils.of( "X-Amzn-Requestid", - "X-Amzn-Errortype" + "X-Amzn-Errortype", + "Content-Length", + "Content-Type" ) ), Arguments.of( diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/mixed-security-service.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/mixed-security-service.openapi.json index a15a581afaf..1a4bd00c703 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/mixed-security-service.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/mixed-security-service.openapi.json @@ -10,7 +10,7 @@ "operationId": "Operation1", "responses": { "200": { - "description": "Operation1 response" + "description": "Operation1 200 response" } } } @@ -20,7 +20,7 @@ "operationId": "Operation2", "responses": { "200": { - "description": "Operation2 response" + "description": "Operation2 200 response" } }, "security": [ @@ -35,7 +35,7 @@ "operationId": "Operation3", "responses": { "200": { - "description": "Operation3 response" + "description": "Operation3 200 response" } }, "security": [ @@ -53,7 +53,7 @@ "operationId": "UnauthenticatedOperation", "responses": { "200": { - "description": "UnauthenticatedOperation response" + "description": "UnauthenticatedOperation 200 response" } }, "security": [] diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-header-mediatype-format.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-header-mediatype-format.openapi.json index 36cba299dae..efad9955e21 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-header-mediatype-format.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-header-mediatype-format.openapi.json @@ -31,7 +31,7 @@ ], "responses": { "200": { - "description": "Operation response" + "description": "Operation 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-header-timestamp-format.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-header-timestamp-format.openapi.json index 2affde37661..84a651d3ed4 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-header-timestamp-format.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-header-timestamp-format.openapi.json @@ -36,7 +36,7 @@ ], "responses": { "200": { - "description": "Operation response" + "description": "Operation 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-json-document-bodies.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-json-document-bodies.openapi.json index ccb7f8b5f24..40e6bf39479 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-json-document-bodies.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-json-document-bodies.openapi.json @@ -28,7 +28,7 @@ ], "responses": { "200": { - "description": "CreateDocument response" + "description": "CreateDocument 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-path-timestamp-format.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-path-timestamp-format.openapi.json index 01f97d2c164..4da8bc43497 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-path-timestamp-format.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-path-timestamp-format.openapi.json @@ -29,7 +29,7 @@ ], "responses": { "200": { - "description": "Operation response" + "description": "Operation 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-query-timestamp-format.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-query-timestamp-format.openapi.json index 048691bd022..7c59cc62322 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-query-timestamp-format.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/adds-query-timestamp-format.openapi.json @@ -40,7 +40,7 @@ ], "responses": { "200": { - "description": "Operation response" + "description": "Operation 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/aws-rest-json-uses-jsonname.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/aws-rest-json-uses-jsonname.openapi.json index b70e1231c65..d1db2d5e154 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/aws-rest-json-uses-jsonname.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/aws-rest-json-uses-jsonname.openapi.json @@ -19,7 +19,7 @@ }, "responses": { "200": { - "description": "CreateDocument response" + "description": "CreateDocument 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/greedy-labels-name-parameter-without-suffix.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/greedy-labels-name-parameter-without-suffix.openapi.json index c82aedee76e..c32d8bd5934 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/greedy-labels-name-parameter-without-suffix.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/greedy-labels-name-parameter-without-suffix.openapi.json @@ -28,7 +28,7 @@ ], "responses": { "200": { - "description": "Operation response" + "description": "Operation 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/greedy-labels.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/greedy-labels.openapi.json index f677f0b1b04..d7a2b9e5f7a 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/greedy-labels.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/protocols/greedy-labels.openapi.json @@ -28,7 +28,7 @@ ], "responses": { "200": { - "description": "Operation response" + "description": "Operation 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/awsv4-security.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/awsv4-security.openapi.json index 94cf79588cb..a0e49a74e7b 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/awsv4-security.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/awsv4-security.openapi.json @@ -10,7 +10,7 @@ "operationId": "Operation1", "responses": { "200": { - "description": "Operation1 response" + "description": "Operation1 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-api-key-bearer-security.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-api-key-bearer-security.openapi.json index e6b722b8c16..f0fb282ae43 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-api-key-bearer-security.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-api-key-bearer-security.openapi.json @@ -10,7 +10,7 @@ "operationId": "Operation", "responses": { "200": { - "description": "Operation response" + "description": "Operation 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-api-key-security.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-api-key-security.openapi.json index 920eac785f6..b09d7b30503 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-api-key-security.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-api-key-security.openapi.json @@ -10,7 +10,7 @@ "operationId": "Operation1", "responses": { "200": { - "description": "Operation1 response" + "description": "Operation1 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-basic-security.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-basic-security.openapi.json index 8464b81e142..2646faa54c3 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-basic-security.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-basic-security.openapi.json @@ -10,7 +10,7 @@ "operationId": "Operation1", "responses": { "200": { - "description": "Operation1 response" + "description": "Operation1 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-digest-security.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-digest-security.openapi.json index 5c0c68323dc..c5edebfa6a9 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-digest-security.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/security/http-digest-security.openapi.json @@ -10,7 +10,7 @@ "operationId": "Operation1", "responses": { "200": { - "description": "Operation1 response" + "description": "Operation1 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-all-tags.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-all-tags.openapi.json index 0663c9fd52e..0a0c1d3c7f1 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-all-tags.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-all-tags.openapi.json @@ -11,7 +11,7 @@ "tags": ["bar", "baz", "foo", "qux"], "responses": { "200": { - "description": "Operation1 response" + "description": "Operation1 200 response" } } } @@ -22,7 +22,7 @@ "tags": ["qux"], "responses": { "200": { - "description": "Operation2 response" + "description": "Operation2 200 response" } } } @@ -32,7 +32,7 @@ "operationId": "Operation3", "responses": { "200": { - "description": "Operation3 response" + "description": "Operation3 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-empty-supported-tags.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-empty-supported-tags.openapi.json index c813741a22f..7d356fd06e0 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-empty-supported-tags.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-empty-supported-tags.openapi.json @@ -10,7 +10,7 @@ "operationId": "Operation1", "responses": { "200": { - "description": "Operation1 response" + "description": "Operation1 200 response" } } } @@ -20,7 +20,7 @@ "operationId": "Operation2", "responses": { "200": { - "description": "Operation2 response" + "description": "Operation2 200 response" } } } @@ -30,7 +30,7 @@ "operationId": "Operation3", "responses": { "200": { - "description": "Operation3 response" + "description": "Operation3 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-supported-tags.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-supported-tags.openapi.json index e89cd85c844..9b6bd0d12af 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-supported-tags.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/tagged-service-supported-tags.openapi.json @@ -11,7 +11,7 @@ "tags": ["baz", "foo"], "responses": { "200": { - "description": "Operation1 response" + "description": "Operation1 200 response" } } } @@ -21,7 +21,7 @@ "operationId": "Operation2", "responses": { "200": { - "description": "Operation2 response" + "description": "Operation2 200 response" } } } @@ -31,7 +31,7 @@ "operationId": "Operation3", "responses": { "200": { - "description": "Operation3 response" + "description": "Operation3 200 response" } } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/unsupported-http-method.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/unsupported-http-method.openapi.json index 35a09d0d097..467a4dddf2e 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/unsupported-http-method.openapi.json +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/unsupported-http-method.openapi.json @@ -10,7 +10,7 @@ "operationId": "Get", "responses": { "200": { - "description": "Get response" + "description": "Get 200 response" } } }, @@ -18,7 +18,7 @@ "operationId": "Put", "responses": { "200": { - "description": "Put response" + "description": "Put 200 response" } } }, @@ -26,7 +26,7 @@ "operationId": "Post", "responses": { "200": { - "description": "Post response" + "description": "Post 200 response" } } }, @@ -34,7 +34,7 @@ "operationId": "Delete", "responses": { "200": { - "description": "Delete response" + "description": "Delete 200 response" } } }, @@ -42,7 +42,7 @@ "operationId": "Options", "responses": { "200": { - "description": "Options response" + "description": "Options 200 response" } } }, @@ -50,7 +50,7 @@ "operationId": "Head", "responses": { "200": { - "description": "Head response" + "description": "Head 200 response" } } }, @@ -58,7 +58,7 @@ "operationId": "Patch", "responses": { "200": { - "description": "Patch response" + "description": "Patch 200 response" } } }, @@ -66,7 +66,7 @@ "operationId": "Trace", "responses": { "200": { - "description": "Trace response" + "description": "Trace 200 response" } } } diff --git a/smithy-protocol-test-traits/src/main/java/software/amazon/smithy/protocoltests/traits/HttpRequestTestsInputValidator.java b/smithy-protocol-test-traits/src/main/java/software/amazon/smithy/protocoltests/traits/HttpRequestTestsInputValidator.java index 6beca1e6278..ad7ed099c47 100644 --- a/smithy-protocol-test-traits/src/main/java/software/amazon/smithy/protocoltests/traits/HttpRequestTestsInputValidator.java +++ b/smithy-protocol-test-traits/src/main/java/software/amazon/smithy/protocoltests/traits/HttpRequestTestsInputValidator.java @@ -34,7 +34,7 @@ public HttpRequestTestsInputValidator() { @Override StructureShape getStructure(Shape shape, OperationIndex operationIndex) { - return operationIndex.getInput(shape).orElse(null); + return operationIndex.expectInputShape(shape); } @Override diff --git a/smithy-protocol-test-traits/src/main/java/software/amazon/smithy/protocoltests/traits/HttpResponseTestsOutputValidator.java b/smithy-protocol-test-traits/src/main/java/software/amazon/smithy/protocoltests/traits/HttpResponseTestsOutputValidator.java index cb31a0ee7a9..5c34f21bdd9 100644 --- a/smithy-protocol-test-traits/src/main/java/software/amazon/smithy/protocoltests/traits/HttpResponseTestsOutputValidator.java +++ b/smithy-protocol-test-traits/src/main/java/software/amazon/smithy/protocoltests/traits/HttpResponseTestsOutputValidator.java @@ -37,7 +37,7 @@ public HttpResponseTestsOutputValidator() { @Override StructureShape getStructure(Shape shape, OperationIndex operationIndex) { - return operationIndex.getOutput(shape).orElse(null); + return operationIndex.getOutputShape(shape).orElse(null); } @Override diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-malformed-request-features.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-malformed-request-features.smithy index cab7599a0f2..d56a7586a7c 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-malformed-request-features.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-malformed-request-features.smithy @@ -101,9 +101,11 @@ structure testProtocol {} } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { @httpPayload body: String, @@ -111,3 +113,6 @@ structure SayHelloInput { @httpHeader("X-OmitMe") header: String, } + +@output +structure SayHelloOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-request-features.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-request-features.smithy index 35a6800803d..cf655ae2674 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-request-features.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-request-features.smithy @@ -37,9 +37,11 @@ structure testScheme {} } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { @httpPayload body: String, @@ -47,3 +49,6 @@ structure SayHelloInput { @httpHeader("X-OmitMe") header: String, } + +@output +structure SayHelloOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-response-features.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-response-features.smithy index 19faec86cad..c8c54aa9ad6 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-response-features.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/all-response-features.smithy @@ -36,9 +36,14 @@ structure testScheme {} } ]) operation GetFoo { + input: GetFooInput, output: GetFooOutput } +@input +structure GetFooInput {} + +@output structure GetFooOutput { @httpPayload bar: String, diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-duplicate-ids.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-duplicate-ids.smithy index 99f9facf099..a285913dce0 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-duplicate-ids.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-duplicate-ids.smithy @@ -21,8 +21,14 @@ structure testProtocol {} } ]) operation SayGoodbye { + input: SayGoodbyeInput, output: SayGoodbyeOutput } + +@input +structure SayGoodbyeInput {} + +@output structure SayGoodbyeOutput {} @httpResponseTests([ @@ -56,10 +62,16 @@ structure MyError {} }, ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } + +@input structure SayHelloInput {} +@output +structure SayHelloOutput {} + @http(method: "POST", uri: "/") @httpRequestTests([ { @@ -76,7 +88,12 @@ structure SayHelloInput {} } ]) operation SayHello2 { - input: SayHelloInput2 + input: SayHello2Input, + output: SayHello2Output } -structure SayHelloInput2 {} +@input +structure SayHello2Input {} + +@output +structure SayHello2Output {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-malformed-request-parameters.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-malformed-request-parameters.smithy index 9b64f4c0834..09a6713afa3 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-malformed-request-parameters.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-malformed-request-parameters.smithy @@ -38,9 +38,11 @@ structure testProtocol {} } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { @httpPayload body: String, @@ -48,3 +50,6 @@ structure SayHelloInput { @httpHeader("X-OmitMe") header: String, } + +@output +structure SayHelloOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-malformed-request-response.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-malformed-request-response.smithy index 67d6130fa0c..a892d753a41 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-malformed-request-response.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-malformed-request-response.smithy @@ -33,9 +33,11 @@ structure testProtocol {} } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { @httpPayload body: String, @@ -43,3 +45,6 @@ structure SayHelloInput { @httpHeader("X-OmitMe") header: String, } + +@output +structure SayHelloOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-params.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-params.smithy index 7442a8831dd..9d52e20dc87 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-params.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/detects-invalid-params.smithy @@ -19,9 +19,14 @@ structure testProtocol {} } ]) operation SayGoodbye { + input: SayGoodbyeInput, output: SayGoodbyeOutput } +@input +structure SayGoodbyeInput {} + +@output structure SayGoodbyeOutput {} @httpResponseTests([ @@ -52,9 +57,14 @@ structure MyError { } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { badType: Boolean } + +@output +structure SayHelloOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-json.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-json.smithy index e23e8005e44..c8e579a1a12 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-json.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-json.smithy @@ -26,9 +26,14 @@ structure testProtocol {} } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { type: Boolean } + +@output +structure SayHelloOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-xml-with-dtd.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-xml-with-dtd.smithy index 1a74ab9d5f7..1c74142b63c 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-xml-with-dtd.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-xml-with-dtd.smithy @@ -38,9 +38,14 @@ structure testProtocol {} } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { type: Boolean } + +@output +structure SayHelloOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-xml.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-xml.smithy index bf72043d37b..1f892d21ebd 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-xml.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/invalid-xml.smithy @@ -32,9 +32,14 @@ structure testProtocol {} } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { type: Boolean } + +@output +structure SayHelloOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/missing-vendor-params-shape.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/missing-vendor-params-shape.smithy index f05b66e7174..20e67491952 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/missing-vendor-params-shape.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/missing-vendor-params-shape.smithy @@ -23,9 +23,14 @@ structure testProtocol {} } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { type: Boolean } + +@output +structure SayHelloOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/timestamp-validation.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/timestamp-validation.smithy index 158da55b9d3..45a946bff53 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/timestamp-validation.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/timestamp-validation.smithy @@ -19,9 +19,14 @@ structure testProtocol {} } ]) operation HasTime { - input: HasTimeInput + input: HasTimeInput, + output: HasTimeOutput } +@input structure HasTimeInput { time: Timestamp, } + +@output +structure HasTimeOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/valid-json.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/valid-json.smithy index 80bd63fe317..fe68403e2bc 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/valid-json.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/valid-json.smithy @@ -26,9 +26,14 @@ structure testProtocol {} } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { type: Boolean } + +@output +structure SayHelloOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/valid-xml.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/valid-xml.smithy index 86a3df9bb21..12e67cf1639 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/valid-xml.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/valid-xml.smithy @@ -32,9 +32,14 @@ structure testProtocol {} } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { type: Boolean } + +@output +structure SayHelloOutput {} diff --git a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/vendor-params-validation.smithy b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/vendor-params-validation.smithy index f9ac6347093..2e44691c37b 100644 --- a/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/vendor-params-validation.smithy +++ b/smithy-protocol-test-traits/src/test/resources/software/amazon/smithy/protocoltests/traits/errorfiles/vendor-params-validation.smithy @@ -28,9 +28,14 @@ structure testProtocol {} } ]) operation SayGoodbye { + input: SayGoodbyeInput, output: SayGoodbyeOutput } +@input +structure SayGoodbyeInput {} + +@output structure SayGoodbyeOutput {} @httpResponseTests([ @@ -84,13 +89,18 @@ structure MyError { } ]) operation SayHello { - input: SayHelloInput + input: SayHelloInput, + output: SayHelloOutput } +@input structure SayHelloInput { type: Boolean } +@output +structure SayHelloOutput {} + structure emptyVendorParamsStructure {} structure simpleVendorParamsStructure { diff --git a/smithy-waiters/src/main/java/software/amazon/smithy/waiters/WaiterMatcherValidator.java b/smithy-waiters/src/main/java/software/amazon/smithy/waiters/WaiterMatcherValidator.java index 078e4e5c753..59cdc3f68e6 100644 --- a/smithy-waiters/src/main/java/software/amazon/smithy/waiters/WaiterMatcherValidator.java +++ b/smithy-waiters/src/main/java/software/amazon/smithy/waiters/WaiterMatcherValidator.java @@ -58,37 +58,21 @@ final class WaiterMatcherValidator implements Matcher.Visitor visitOutput(Matcher.OutputMember outputPath) { - StructureShape struct = OperationIndex.of(model).getOutput(operation).orElse(null); - if (struct == null) { - addEvent(Severity.ERROR, NON_SUPPRESSABLE_ERROR, "output path used on operation with no output"); - } else { - validatePathMatcher(createCurrentNodeFromShape(struct), outputPath.getValue()); - } + StructureShape struct = OperationIndex.of(model).expectOutputShape(operation); + validatePathMatcher(createCurrentNodeFromShape(struct), outputPath.getValue()); return events; } @Override public List visitInputOutput(Matcher.InputOutputMember inputOutputMember) { OperationIndex index = OperationIndex.of(model); - - StructureShape input = index.getInput(operation).orElse(null); - if (input == null) { - addEvent(Severity.ERROR, NON_SUPPRESSABLE_ERROR, "inputOutput path used on operation with no input"); - } - - StructureShape output = index.getOutput(operation).orElse(null); - if (output == null) { - addEvent(Severity.ERROR, NON_SUPPRESSABLE_ERROR, "inputOutput path used on operation with no output"); - } - - if (input != null && output != null) { - Map composedMap = new LinkedHashMap<>(); - composedMap.put("input", createCurrentNodeFromShape(input).expectObjectValue()); - composedMap.put("output", createCurrentNodeFromShape(output).expectObjectValue()); - LiteralExpression composedData = new LiteralExpression(composedMap); - validatePathMatcher(composedData, inputOutputMember.getValue()); - } - + StructureShape input = index.expectInputShape(operation); + StructureShape output = index.expectOutputShape(operation); + Map composedMap = new LinkedHashMap<>(); + composedMap.put("input", createCurrentNodeFromShape(input).expectObjectValue()); + composedMap.put("output", createCurrentNodeFromShape(output).expectObjectValue()); + LiteralExpression composedData = new LiteralExpression(composedMap); + validatePathMatcher(composedData, inputOutputMember.getValue()); return events; } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/cannot-wait-on-streaming-operations.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/cannot-wait-on-streaming-operations.smithy index a9032976c34..53d9c6f506f 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/cannot-wait-on-streaming-operations.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/cannot-wait-on-streaming-operations.smithy @@ -13,11 +13,13 @@ use smithy.waiters#waitable ] } ) +@suppress(["OperationMissingOutput"]) operation StreamingInput { - input: StreamingInputOutput + input: StreamingInputInput } -structure StreamingInputOutput { +@input +structure StreamingInputInput { messages: Messages, } @@ -39,6 +41,12 @@ structure SuccessMessage {} ] } ) +@suppress(["OperationMissingInput"]) operation StreamingOutput { - input: StreamingInputOutput + output: StreamingOutputOutput +} + +@output +structure StreamingOutputOutput { + messages: Messages, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/emits-danger-and-warning-typechecks.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/emits-danger-and-warning-typechecks.smithy index 92bcbe73c89..c7e43e0d85d 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/emits-danger-and-warning-typechecks.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/emits-danger-and-warning-typechecks.smithy @@ -34,9 +34,14 @@ use smithy.waiters#waitable } ) operation A { + input: AInput, output: AOutput } +@input +structure AInput {} + +@output structure AOutput { foo: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-input.errors b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-input.errors new file mode 100644 index 00000000000..e69de29bb2d diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-input.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-input.smithy similarity index 92% rename from smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-input.smithy rename to smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-input.smithy index f42d926e4d9..5ed6d899ccb 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-input.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-input.smithy @@ -19,10 +19,12 @@ use smithy.waiters#waitable ] } ) +@suppress(["OperationMissingInput"]) operation A { output: AOutput } +@output structure AOutput { foo: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-output.errors b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-output.errors new file mode 100644 index 00000000000..7608655e82f --- /dev/null +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-output.errors @@ -0,0 +1 @@ +[DANGER] smithy.example#A: Waiter `A`, acceptor 0: Problem found in JMESPath expression (output.foo == 'hi'): Object field 'foo' does not exist in object with properties [] (1:8) | WaitableTraitJmespathProblem diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-output.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-output.smithy similarity index 92% rename from smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-output.smithy rename to smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-output.smithy index b59e9a1f327..926aaff5908 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-output.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/inputOutput-operation-with-no-output.smithy @@ -19,10 +19,12 @@ use smithy.waiters#waitable ] } ) +@suppress(["OperationMissingOutput"]) operation A { input: AInput } +@input structure AInput { foo: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-boolean-expected-value.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-boolean-expected-value.smithy index 1421eaa42b0..b6113f281cf 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-boolean-expected-value.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-boolean-expected-value.smithy @@ -40,10 +40,15 @@ use smithy.waiters#waitable } ) operation A { + input: AInput, output: AOutput, errors: [OhNo], } +@input +structure AInput {} + +@output structure AOutput { foo: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-errorType.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-errorType.smithy index e36e8cd61c8..a27cebacc50 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-errorType.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-errorType.smithy @@ -16,10 +16,15 @@ use smithy.waiters#waitable } ) operation A { + input: AInput, output: AOutput, errors: [OhNo], } +@input +structure AInput {} + +@output structure AOutput { foo: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-input.errors b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-input.errors deleted file mode 100644 index a9db23bb393..00000000000 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-input.errors +++ /dev/null @@ -1 +0,0 @@ -[ERROR] smithy.example#A: Waiter `A`, acceptor 0: inputOutput path used on operation with no input | WaitableTrait diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-output.errors b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-output.errors deleted file mode 100644 index 70a55e529eb..00000000000 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputOutput-operation-with-no-output.errors +++ /dev/null @@ -1 +0,0 @@ -[ERROR] smithy.example#A: Waiter `A`, acceptor 0: inputOutput path used on operation with no output | WaitableTrait diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputoutput-path.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputoutput-path.smithy index 34a2b1b4b4f..08ae516b4d1 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputoutput-path.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-inputoutput-path.smithy @@ -39,10 +39,12 @@ operation A { output: AOutput, } +@input structure AInput { foo: String, } +@output structure AOutput { baz: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-jmespath-syntax.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-jmespath-syntax.smithy index 2d9f6568bb5..2c2b9422d13 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-jmespath-syntax.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-jmespath-syntax.smithy @@ -37,9 +37,14 @@ use smithy.waiters#waitable } ) operation A { + input: AInput, output: AOutput } +@input +structure AInput {} + +@output structure AOutput { foo: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-output-structure-member-access.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-output-structure-member-access.smithy index aacec15a31b..f36cdac2496 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-output-structure-member-access.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-output-structure-member-access.smithy @@ -23,10 +23,12 @@ operation A { output: AOutput, } +@input structure AInput { foo: String, } +@output structure AOutput { baz: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-return-types.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-return-types.smithy index daf612f9e1e..250470439fb 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-return-types.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/invalid-return-types.smithy @@ -50,9 +50,14 @@ use smithy.waiters#waitable } ) operation A { + input: AInput, output: AOutput } +@input +structure AInput {} + +@output structure AOutput { foo: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/minDelay-greater-than-maxDelay.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/minDelay-greater-than-maxDelay.smithy index b2a0711878d..87725bc6cf0 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/minDelay-greater-than-maxDelay.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/minDelay-greater-than-maxDelay.smithy @@ -38,9 +38,14 @@ use smithy.waiters#waitable } ) operation A { + input: AInput, output: AOutput } +@input +structure AInput {} + +@output structure AOutput { foo: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/not-uppercamelcase.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/not-uppercamelcase.smithy index 5fa17e81123..44d606cf2c1 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/not-uppercamelcase.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/not-uppercamelcase.smithy @@ -20,9 +20,14 @@ use smithy.waiters#waitable } ) operation A { + input: AInput, output: AOutput, } +@input +structure AInput {} + +@output structure AOutput { baz: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/output-on-bad-shapes.errors b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/output-on-bad-shapes.errors index 86bcb9ada8f..dd097ad2b77 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/output-on-bad-shapes.errors +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/output-on-bad-shapes.errors @@ -1 +1 @@ -[ERROR] smithy.example#A: Waiter `A`, acceptor 0: output path used on operation with no output | WaitableTrait +[DANGER] smithy.example#A: Waiter `A`, acceptor 0: Problem found in JMESPath expression (foo == 'hey'): Object field 'foo' does not exist in object with properties [] (1:1) | WaitableTraitJmespathProblem diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/output-on-bad-shapes.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/output-on-bad-shapes.smithy index 1b0e401aa7a..9aaded7da3e 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/output-on-bad-shapes.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/output-on-bad-shapes.smithy @@ -18,4 +18,5 @@ use smithy.waiters#waitable ] } ) +@suppress(["OperationMissingInput", "OperationMissingOutput"]) operation A {} diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/valid-inputoutput.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/valid-inputoutput.smithy index 1097e89d925..d2a4dc28b16 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/valid-inputoutput.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/valid-inputoutput.smithy @@ -24,10 +24,12 @@ operation A { output: AOutput } +@input structure AInput { foo: String, } +@output structure AOutput { baz: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/valid-waiters.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/valid-waiters.smithy index 8b9be83cba8..df7a7c81494 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/valid-waiters.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/valid-waiters.smithy @@ -116,10 +116,15 @@ use smithy.waiters#waitable } ) operation A { + input: AInput, output: AOutput, errors: [OhNo], } +@input +structure AInput {} + +@output structure AOutput { foo: String, } diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/waiter-missing-success-state.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/waiter-missing-success-state.smithy index 3efa385a537..6b7fa7cad36 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/waiter-missing-success-state.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/waiter-missing-success-state.smithy @@ -16,8 +16,12 @@ use smithy.waiters#waitable } ) operation A { - input: AInputOutput, - output: AInputOutput + input: AInput, + output: AOutput } -structure AInputOutput {} +@input +structure AInput {} + +@output +structure AOutput {} diff --git a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/waiter-name-conflicts.smithy b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/waiter-name-conflicts.smithy index d3ff7401045..3a8f4c70d03 100644 --- a/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/waiter-name-conflicts.smithy +++ b/smithy-waiters/src/test/resources/software/amazon/smithy/waiters/errorfiles/waiter-name-conflicts.smithy @@ -39,9 +39,14 @@ service InvalidService { } ) operation A { + input: AInput, output: AOutput, } +@input +structure AInput {} + +@output structure AOutput { foo: String, } @@ -78,5 +83,14 @@ structure AOutput { } ) operation B { - output: AOutput, + input: BInput, + output: BOutput, +} + +@input +structure BInput {} + +@output +structure BOutput { + foo: String, }