From 627e7e97e9a7cced278658dd56006717954c52f0 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Sun, 12 Jul 2020 08:04:07 -0700 Subject: [PATCH] Refactor the spec to better explain the semantic model The semantic model is a much more important concept now that we have two representations for models (the AST and IDL), and these representations are meant to build up the same graph of shapes regardless of their different syntactic features. The spec is also now laid out in an easier to understand way since it explains the high-level concepts, shapes, and traits before diving into the details of the IDL ABNF. Various aspects of the model have been clarified: * Applying traits externally is the same as applying them inline in the semantic model. * Simplifications to the documentation_comment ABNF so that it is now just one of the two kinds of `comment` productions. * A broader overview of the model, including transformations. * More information on namespaces, including that there can be multiple namespaces loaded into the semantic model. * More information on how and why models would be merged together (it's left up to tooling to combine and merge models, but the semantics of merging models are described in the spec). * Lots of minor error corrections in the spec. * The special behavior around resolving relative shape IDs in the "conflicts" property of the `trait` trait have been removed since that is at odds with the rest of the model. This commit moves various headings around and removes some pages, but redirects are issued when a removed page is visited. This commit also ensures that requirements.txt is used for docs. Since we're using a github repo for the redirects package, we need to install it using a special syntax. There some docs around how to pull this off with setup.py, but I couldn't get it to work: https://python-packaging.readthedocs.io/en/latest/dependencies.html#packages-not-on-pypi --- docs/Makefile | 1 + docs/requirements.txt | 3 + docs/smithy/__init__.py | 54 +- .../guides/building-models/build-config.rst | 8 +- docs/source/1.0/guides/style-guide.rst | 2 +- .../1.0/spec/aws/aws-ec2-query-protocol.rst | 2 +- .../1.0/spec/aws/aws-query-protocol.rst | 2 +- .../1.0/spec/aws/aws-restxml-protocol.rst | 2 +- docs/source/1.0/spec/core/http-traits.rst | 2 +- docs/source/1.0/spec/core/idl.rst | 1888 ++++++++++++ docs/source/1.0/spec/core/index.rst | 79 +- docs/source/1.0/spec/core/intro.rst | 86 - docs/source/1.0/spec/core/json-ast.rst | 10 +- .../1.0/spec/core/lexical-structure.rst | 910 ------ docs/source/1.0/spec/core/merging-models.rst | 18 - docs/source/1.0/spec/core/model-metadata.rst | 88 - docs/source/1.0/spec/core/model.rst | 2631 +++++++++++++++++ docs/source/1.0/spec/core/prelude-model.rst | 5 +- docs/source/1.0/spec/core/protocol-traits.rst | 30 +- docs/source/1.0/spec/core/resource-traits.rst | 85 +- docs/source/1.0/spec/core/selectors.rst | 2 +- docs/source/1.0/spec/core/shapes.rst | 2186 -------------- docs/source/1.0/spec/core/traits.rst | 694 ----- .../1.0/spec/core/type-refinement-traits.rst | 2 +- docs/source/1.0/spec/index.rst | 5 +- docs/source/conf.py | 2 + docs/source/quickstart.rst | 6 +- docs/source/redirects | 6 + .../themes/smithy/static/bootstrap-reboot.css | 2 +- docs/themes/smithy/static/default.css_t | 107 +- .../smithy/model/traits/TraitDefinition.java | 7 - .../smithy/model/loader/prelude-traits.smithy | 4 +- 32 files changed, 4839 insertions(+), 4090 deletions(-) create mode 100644 docs/source/1.0/spec/core/idl.rst delete mode 100644 docs/source/1.0/spec/core/intro.rst delete mode 100644 docs/source/1.0/spec/core/lexical-structure.rst delete mode 100644 docs/source/1.0/spec/core/merging-models.rst delete mode 100644 docs/source/1.0/spec/core/model-metadata.rst create mode 100644 docs/source/1.0/spec/core/model.rst delete mode 100644 docs/source/1.0/spec/core/shapes.rst delete mode 100644 docs/source/1.0/spec/core/traits.rst create mode 100644 docs/source/redirects diff --git a/docs/Makefile b/docs/Makefile index 935a5364301..da038a50d19 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -10,6 +10,7 @@ help: @echo " openhtml to preview the built index.html" install: + pip install -r requirements.txt . pip install -e . clean: diff --git a/docs/requirements.txt b/docs/requirements.txt index b68a3664c21..c17ecda939e 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,6 @@ sphinx>=1.7.0,<1.8.0 pygments==2.4.2 sphinx-tabs==1.1.7 + +# This package has not been published to pypi +-e git+https://github.com/munnerz/redirects.git#egg=sphinxcontrib-redirects diff --git a/docs/smithy/__init__.py b/docs/smithy/__init__.py index 735251f5131..c0605c7b624 100644 --- a/docs/smithy/__init__.py +++ b/docs/smithy/__init__.py @@ -1,13 +1,17 @@ import re -from sphinx.writers.html import HTMLTranslator as SphinxHTMLTranslator -from docutils.writers.html4css1 import HTMLTranslator as BaseTranslator -from docutils import nodes from sphinx.locale import _ +from sphinx.writers.html import HTMLTranslator as SphinxHTMLTranslator + +from docutils import nodes +from docutils.parsers.rst import Directive, directives +from docutils.statemachine import StringList +from docutils.writers.html4css1 import HTMLTranslator as BaseTranslator def setup(app): app.set_translator('html', HTMLTranslator) + app.add_directive("text-figure", TextFigure) # Finds the href part of a header. HREF = re.compile('href="([^"]+)"') @@ -51,3 +55,47 @@ def visit_productionlist(self, node): self.body.append('\n') self.body.append('\n') raise nodes.SkipNode + + +# Use .. text-figure to create text diagrams that look like figures. +class TextFigure(Directive): + + required_arguments = 0 + optional_arguments = 2 + option_spec = { + 'caption': directives.unchanged_required, + 'name': directives.unchanged, + } + has_content = True + final_argument_whitespace = False + + def run(self): + self.assert_has_content() + text = '\n'.join(self.content) + literal = nodes.literal_block(text, text) + literal = self.__container_wrapper(literal, self.options.get('caption')) + self.add_name(literal) + literal['classes'].append("text-figure") + return [literal] + + # This is a modified version of https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/directives/code.py + # that places the caption after the text rather than before. + def __container_wrapper(self, literal_node, caption): + container_node = nodes.container('', literal_block=True, + classes=['literal-block-wrapper']) + parsed = nodes.Element() + self.state.nested_parse(StringList([caption], source=''), + self.content_offset, parsed) + if isinstance(parsed[0], nodes.system_message): + msg = __('Invalid caption: %s' % parsed[0].astext()) + raise ValueError(msg) + elif isinstance(parsed[0], nodes.Element): + caption_node = nodes.caption(parsed[0].rawsource, '', + *parsed[0].children) + caption_node.source = literal_node.source + caption_node.line = literal_node.line + container_node += literal_node + container_node += caption_node + return container_node + else: + raise RuntimeError # never reached diff --git a/docs/source/1.0/guides/building-models/build-config.rst b/docs/source/1.0/guides/building-models/build-config.rst index 50cbd5146a5..177bcecad38 100644 --- a/docs/source/1.0/guides/building-models/build-config.rst +++ b/docs/source/1.0/guides/building-models/build-config.rst @@ -746,8 +746,8 @@ orphaned shapes. excludeMetadata --------------- -Removes :ref:`metadata` key-value pairs from a model if the key is in the -provided ``keys`` list. +Removes model :ref:`metadata ` key-value pairs from a model if the +key is in the provided ``keys`` list. .. list-table:: :header-rows: 1 @@ -786,8 +786,8 @@ provided ``keys`` list. includeMetadata --------------- -Removes :ref:`metadata` key-value pairs from a model if the key is not in -the provided ``keys`` list. +Removes model :ref:`metadata ` key-value pairs from a model if the +key is not in the provided ``keys`` list. .. list-table:: :header-rows: 1 diff --git a/docs/source/1.0/guides/style-guide.rst b/docs/source/1.0/guides/style-guide.rst index 75f4bfdb24c..eff772c7797 100644 --- a/docs/source/1.0/guides/style-guide.rst +++ b/docs/source/1.0/guides/style-guide.rst @@ -14,7 +14,7 @@ style guide makes models easier to read. Model files =========== -Smithy models SHOULD be authored using the :ref:`Smithy IDL `. +Smithy models SHOULD be authored using the :ref:`Smithy IDL `. Smithy models SHOULD resemble the following example: .. code-block:: smithy 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 22963f915f2..52de2616157 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 @@ -29,7 +29,7 @@ Value type .. important:: - This protocol does not support :ref:`inline document types `. + This protocol does not support document types. .. tabs:: 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 928680a0e08..3b2a5a10563 100644 --- a/docs/source/1.0/spec/aws/aws-query-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-query-protocol.rst @@ -58,6 +58,6 @@ See .. important:: - This protocol does not support :ref:`inline document types `. + This protocol does not support document types. *TODO: Add specifications, protocol examples, etc.* diff --git a/docs/source/1.0/spec/aws/aws-restxml-protocol.rst b/docs/source/1.0/spec/aws/aws-restxml-protocol.rst index 52bae86ac05..ebde542e0cb 100644 --- a/docs/source/1.0/spec/aws/aws-restxml-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-restxml-protocol.rst @@ -194,7 +194,7 @@ that affect serialization: .. important:: - This protocol does not support :ref:`inline document types `. + This protocol does not support document types. ------------ diff --git a/docs/source/1.0/spec/core/http-traits.rst b/docs/source/1.0/spec/core/http-traits.rst index 9aea2b62bb8..a2be5e6084f 100644 --- a/docs/source/1.0/spec/core/http-traits.rst +++ b/docs/source/1.0/spec/core/http-traits.rst @@ -670,7 +670,7 @@ Serialization rules: #. When a string or blob member is referenced, the raw value is serialized as the body of the message. #. When a :ref:`structure `, :ref:`union `, or - :ref:`document ` is targeted, the shape value is serialized + document type is targeted, the shape value is serialized as a :ref:`protocol-specific ` document that is sent as the body of the message. diff --git a/docs/source/1.0/spec/core/idl.rst b/docs/source/1.0/spec/core/idl.rst new file mode 100644 index 00000000000..3b8cbf1fee1 --- /dev/null +++ b/docs/source/1.0/spec/core/idl.rst @@ -0,0 +1,1888 @@ +.. _idl: + +========== +Smithy IDL +========== + +Smithy models are defined using either the Smithy interface definition language +(IDL) or the :ref:`JSON abstract syntax tree ` (AST). This document +defines the ABNF_ grammar and syntax for defining models with the Smithy IDL. + +.. contents:: Table of contents + :depth: 1 + :local: + :backlinks: none + + +------------------- +Smithy IDL overview +------------------- + +The Smithy IDL is made up of 3, ordered sections, each of which is optional: + +1. **Control section**; defines parser directives like which version of the + IDL to use. +2. **Metadata section**; applies metadata to the entire model. +3. **Shape section**; where shapes and traits are defined. A namespace MUST + be defined before any shapes or traits can be defined. + :token:`use_statement`\s can be defined after a namespace and before shapes + or traits to refer to shapes in other namespaces using a shorter name. + +The following example defines a model file with each section: + +.. tabs:: + + .. code-tab:: smithy + + // (1) Control section + $version: "1.0" + + // (2) Metadata section + metadata foo = "bar" + + // (3) Shape section + namespace smithy.example + + use smithy.other.namespace#MyString + + structure MyStructure { + @required + foo: MyString + } + + .. code-tab:: json + + { + "smithy": "1.0", + "metadata": { + "foo": "bar" + }, + "shapes": { + "smithy.example#MyStructure": { + "type": "structure", + "members": { + "foo": { + "target": "smithy.other.namespace#MyString", + "traits": { + "smithy.api#required": {} + } + } + } + } + } + } + + +------------- +Lexical notes +------------- + +Smithy models MUST be encoded using UTF-8 and SHOULD use Unix style +line endings (``\n``). The Smithy ABNF is whitespace sensitive. + + +.. _smithy-idl-abnf: + +--------------- +Smithy IDL ABNF +--------------- + +The Smithy IDL is defined by the following ABNF: + +.. productionlist:: smithy + idl:`ws` `control_section` `metadata_section` `shape_section` + +.. rubric:: Whitespace + +.. productionlist:: smithy + ws :*(`sp` / `newline` / `comment`) ; whitespace + sp :*(%x20 / %x09) ; " " and \t + br :`sp` (`comment` / `newline`) `sp` ; break + newline :%x0A / %x0D.0A ; \n and \r\n + +.. rubric:: Comments + +.. productionlist:: smithy + comment: `documentation_comment` / `comment` + documentation_comment:"///" *`not_newline` `br` + line_comment: "//" *`not_newline` `newline` + not_newline: %x09 / %x20-10FFFF ; Any character except newline + +.. rubric:: Control + +.. productionlist:: smithy + control_section :*(`control_statement`) + control_statement :"$" `ws` `node_object_key` `ws` ":" `ws` `node_value` `br` + +.. rubric:: Metadata + +.. productionlist:: smithy + metadata_section :*(`metadata_statement`) + metadata_statement :"metadata" `ws` `node_object_key` `ws` "=" `ws` `node_value` `br` + +.. rubric:: Node values + +.. productionlist:: smithy + node_value :`node_array` + :/ `node_object` + :/ `number` + :/ `node_keywords` + :/ `node_string_value` + node_array :`empty_node_array` / `populated_node_array` + empty_node_array :"[" `ws` "]" + populated_node_array:"[" `ws` `node_value` `ws` + : *(`comma` `node_value` `ws`) + : `trailing_comma` "]" + trailing_comma :[`comma`] + comma :"," `ws` + node_object :`empty_node_object` / `populated_node_object` + empty_node_object :"{" `ws` "}" + populated_node_object:"{" `ws` `node_object_kvp` `ws` + : *(`comma` `node_object_kvp` `ws`) + : `trailing_comma` "}" + node_object_kvp :`node_object_key` `ws` ":" `ws` `node_value` + node_object_key :`quoted_text` / `identifier` + number :[`minus`] `int` [`frac`] [`exp`] + decimal_point :%x2E ; . + digit1_9 :%x31-39 ; 1-9 + e :%x65 / %x45 ; e E + exp :`e` [`minus` / `plus`] 1*DIGIT + frac :`decimal_point` 1*DIGIT + int :`zero` / (`digit1_9` *DIGIT) + minus :%x2D ; - + plus :%x2B ; + + zero :%x30 ; 0 + node_keywords: "true" / "false" / "null" + node_string_value :`shape_id` / `text_block` / `quoted_text` + quoted_text :DQUOTE *`quoted_char` DQUOTE + quoted_char :%x20-21 ; space - "!" + :/ %x23-5B ; "#" - "[" + :/ %x5D-10FFFF ; "]"+ + :/ `escaped_char` + :/ `preserved_double` + escaped_char :`escape` (`escape` / "'" / DQUOTE / "b" / "f" / "n" / "r" / "t" / "/" / `unicode_escape`) + unicode_escape :"u" `hex` `hex` `hex` `hex` + hex : DIGIT / %x41-46 / %x61-66 + preserved_double :`escape` (%x20-21 / %x23-5B / %x5D-10FFFF) + escape :%x5C ; backslash + text_block :`three_dquotes` `br` *`quoted_char` `three_dquotes` + three_dquotes :DQUOTE DQUOTE DQUOTE + +.. rubric:: Shapes + +.. productionlist:: smithy + shape_section :[`namespace_statement` [`use_section`] [`shape_statements`]] + namespace_statement :"namespace" `ws` `namespace` `br` + use_section :*(`use_statement`) + use_statement :"use" `ws` `absolute_root_shape_id` `br` + shape_statements :*(`shape_statement` / `apply_statement`) + shape_statement :`trait_statements` `shape_body` `br` + shape_body :`simple_shape_statement` + :/ `list_statement` + :/ `set_statement` + :/ `map_statement` + :/ `structure_statement` + :/ `union_statement` + :/ `service_statement` + :/ `operation_statement` + :/ `resource_statement` + simple_shape_statement :`simple_type_name` `ws` `identifier` + simple_type_name :"blob" / "boolean" / "document" / "string" + :/ "byte" / "short" / "integer" / "long" + :/ "float" / "double" / "bigInteger" + :/ "bigDecimal" / "timestamp" + shape_members :`empty_shape_members` / `populated_shape_members` + empty_shape_members :"{" `ws` "}" + populated_shape_members :"{" `ws` `shape_member_kvp` + : *(`comma` `shape_member_kvp` `ws`) `trailing_comma` "}" + shape_member_kvp :`trait_statements` `identifier` `ws` ":" `ws` `shape_id` + list_statement :"list" `ws` `identifier` `ws` `shape_members` + set_statement :"set" `ws` `identifier` `ws` `shape_members` + map_statement :"map" `ws` `identifier` `ws` `shape_members` + structure_statement :"structure" `ws` `identifier` `ws` `shape_members` + union_statement :"union" `ws` `identifier` `ws` `shape_members` + service_statement :"service" `ws` `identifier` `ws` `node_object` + operation_statement :"operation" `ws` `identifier` `ws` `node_object` + resource_statement :"resource" `ws` `identifier` `ws` `node_object` + +.. rubric:: Traits + +.. productionlist:: smithy + trait_statements : *(`ws` `trait`) `ws` + trait :"@" `shape_id` [`trait_body`] + trait_body :"(" `ws` `trait_body_value` `ws` ")" + trait_body_value :`trait_structure` / `node_value` + trait_structure :`trait_structure_kvp` *(`ws` `comma` `trait_structure_kvp`) + trait_structure_kvp :`node_object_key` `ws` ":" `ws` `node_value` + apply_statement :"apply" `ws` `shape_id` `ws` `trait` `br` + + +.. _comments: + +-------- +Comments +-------- + +A :token:`comment ` can appear at any place between tokens where +whitespace (:token:`ws`) can appear. Comments in Smithy are defined using two +forward slashes followed by any character. A newline terminates a comment. + +.. code-block:: smithy + + // This is a comment + namespace com.foo // This is also a comment + + // Another comment + string MyString + +.. note:: + + Three forward slashes can be used to define the documentation of a shape + using a special :ref:`documentation comment `. + + +.. _control-statement: + +--------------- +Control section +--------------- + +The :token:`control section ` of a model contains +:token:`control statements ` that apply parser directives +to a *specific IDL file*. Because control statements influence parsing, they +MUST appear at the beginning of a file before any other statements and have +no effect on the :ref:`semantic model ` + +The :ref:`version ` statement is currently the only control +statement defined in the Smithy IDL. Implementations MUST ignore unknown +control statements. + + +.. _smithy-version: + +Version statement +================= + +The Smithy specification is versioned using a ``major`` . ``minor`` +versioning scheme. A version requirement is specified for a model file using +the ``$version`` control statement. When no version number is specified in +the IDL, an implementation SHOULD assume that the model can be loaded. +Because this can lead to unexpected parsing errors, models SHOULD always +include a version. + +The value provided in a version control statement is a string that MUST +adhere to the following ABNF: + +.. productionlist:: smithy + version_string :1*DIGIT [ "." 1*DIGIT ] + +The following example sets the version to ``1``, meaning that tooling MUST +support a version greater than or equal to ``1.0`` and less than ``2.0``: + +.. tabs:: + + .. code-tab:: smithy + + $version: "1" + + .. code-tab:: json + + { + "smithy": "1" + } + +A minor version SHOULD be provided when a model depends on a feature released +in a minor update of the specification. The following example sets the +version requirement of a file to ``1.1``, meaning that tooling MUST support a +version greater than or equal to ``1.1`` and less than ``2.0``: + +.. tabs:: + + .. code-tab:: smithy + + $version: "1.1" + + .. code-tab:: json + + { + "smithy": "1.1" + } + +.. rubric:: Version compatibility + +A single version statement can appear in a model file, but different versions +MAY be encountered when merging multiple model files together. Multiple +versions are supported if and only if all of the version statements are +supported by the tool loading the models. + + +.. _metadata-section: + +---------------- +Metadata section +---------------- + +The :token:`metadata section ` is used to apply untyped +:ref:`metadata ` to the entire model. A :token:`metadata_statement` +consists of the metadata key to set, followed by ``=``, followed by the +:token:`node value ` to assign to the key. + +The following example defines metadata in the model: + +.. tabs:: + + .. code-tab:: smithy + + metadata greeting = "hello" + metadata "stringList" = ["a", "b", "c"] + + .. code-tab:: json + + { + "smithy": "1.0", + "metadata": { + "greeting": "hello", + "stringList": ["a", "b", "c"] + } + } + + +------------- +Shape section +------------- + +The :token:`shape section ` of the IDL is used to define +shapes and apply traits to shapes. + + +.. _namespaces: + +Namespaces +========== + +Shapes can only be defined after a namespace is declared. A namespace is +declared using a :token:`namespace statement `. Only +one namespace can appear per file. + +The following example defines a string shape named ``MyString`` in the +``smithy.example`` namespace: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + string MyString + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyString": { + "type": "string" + } + } + } + + +.. _use-statement: + +Referring to shapes +=================== + +The :token:`use section ` of the IDL is used to import shapes +into the current namespace so that they can be referred to using a +:ref:`relative shape ID `. The :token:`use_statement `\s +that make up this section have no effect on the :ref:`semantic model `. + +The following example uses ``smithy.example#Foo`` and ``smithy.example#Baz`` +so that they can be referred to using only ``Foo`` and ``Baz``. + +.. code-block:: smithy + + namespace smithy.hello + + use smithy.example#Foo + use smithy.example#Baz + + map MyMap { + // Resolves to smithy.example#Foo + key: Foo, + // Resolves to smithy.example#Baz + value: Baz, + } + +A use statement can refer to :ref:`traits ` too. The following example +uses the ``smithy.example#test`` and ``smithy.example#anotherTrait`` +traits so that they can be applied using relative shape IDs: + +.. code-block:: smithy + + namespace smithy.hello + + use smithy.example#test + use smithy.example#anotherTrait + + @test // <-- Resolves to smithy.example#test + string MyString + +.. rubric:: Use statement validation + +#. A shape cannot be defined in a file with the same name as one of the + shapes imported with a ``use`` statement. +#. Shapes IDs with members names cannot be imported with a use statement. + + +.. _relative-shape-id: + +Relative shape ID resolution +---------------------------- + +Relative shape IDs are resolved using the following process: + +#. If a :token:`use_statement` has imported a shape with the same name, + the shape ID resolves to the imported shape ID. +#. If a shape is defined in the same namespace as the shape with the same name, + the namespace of the shape resolves to the *current namespace*. +#. If a shape is defined in the :ref:`prelude ` with the same name, + the namespace resolves to ``smithy.api``. +#. If a relative shape ID does not satisfy one of the above cases, the shape + ID is invalid, and the namespace is inherited from the *current namespace*. + +The following example Smithy model contains comments above each member of +the shape named ``MyStructure`` that describes the shape the member resolves +to. + +.. code-block:: smithy + + namespace smithy.example + + use foo.baz#Bar + + string MyString + + structure MyStructure { + // Resolves to smithy.example#MyString + // There is a shape named MyString defined in the same namespace. + a: MyString, + + // Resolves to smithy.example#MyString + // Absolute shape IDs do not perform namespace resolution. + b: smithy.example#MyString, + + // Resolves to foo.baz#Bar + // The "use foo.baz#Bar" statement imported the Bar symbol, + // allowing the shape to be referenced using a relative shape ID. + c: Bar, + + // Resolves to smithy.api#String + // No shape named String was imported through a use statement + // the smithy.example namespace does not contain a shape named + // String, and the prelude model contains a shape named String. + d: String, + + // Resolves to smithy.example#MyBoolean. + // There is a shape named MyBoolean defined in the same namespace. + // Forward references are supported both within the same file and + // across multiple files. + e: MyBoolean, + + // Resolves to smithy.example#InvalidShape. A shape by this name has + // not been imported through a use statement, a shape by this name + // does not exist in the current namespace, and a shape by this name + // does not exist in the prelude model. + f: InvalidShape, + } + + boolean MyBoolean + + +.. _syntactic-shape-ids: + +Syntactic shape IDs +------------------- + +Unquoted string values that are not object keys in the Smithy IDL are +considered lexical shape IDs and are resolved to absolute shape IDs using the +process defined in :ref:`relative-shape-id`. + +The following model defines a list that references a string shape defined +in another namespace. + +.. code-block:: smithy + + namespace smithy.example + + use smithy.other#MyString + + list MyList { + member: MyString + } + +The above model is equivalent to the following JSON AST model: + +.. code-block:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyList": { + "type": "list", + "members": { + "target": "smithy.other#MyString" + } + } + } + } + +.. rubric:: Use quotes for literal strings + +Values that are not meant to be shape IDs MUST be quoted. The following +model is syntactically valid but semantically incorrect because +it resolves the value of the :ref:`error-trait` to the shape ID +``"smithy.example#client"`` rather than using the string literal value of +``"client"``: + +.. code-block:: smithy + + namespace smithy.example + + @error(client) // <-- This MUST be "client" + structure Error + + string client + +The above example is equivalent to the following incorrect JSON AST: + +.. code-block:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#Error": { + "type": "structure", + "traits": { + "smithy.api#error": "smithy.example#client" + } + }, + "smithy.example#client": { + "type": "string" + } + } + } + +.. rubric:: Object keys + +Object keys are not treated as shape IDs. The following example defines a +:ref:`metadata ` object, and when loaded into the +:ref:`semantic model `, the object key ``String`` remains +the same literal string value of ``String`` while the value is treated as +a shape ID and resolves to the string literal ``"smithy.api#String"``. + +.. code-block:: smithy + + metadata foo = { + String: String, + } + +The above example is equivalent to the following JSON AST: + +.. code-block:: json + + { + "smithy": "1.0", + "metadata": { + "String": "smithy.api#String" + } + } + +.. rubric:: Semantic model + +Syntactic shape IDs are syntactic sugar for defining fully-qualified +shape IDs inside of strings, and this difference is inconsequential in the +:ref:`semantic model `. A syntactic shape ID SHOULD be +resolved to a string that contains a fully-qualified shape ID when parsing +the model. + + +Defining shapes +=============== + +Shapes are defined using a :token:`shape_statement`. + + +.. _idl-simple: + +Simple shapes +------------- + +:ref:`Simple shapes ` are defined using a +:token:`simple_shape_statement`. + +The following example defines a ``string`` shape: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + string MyString + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#String": { + "type": "string" + } + } + } + +The following example defines an ``integer`` shape with a :ref:`range-trait`: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + @range(min: 0, max: 1000) + integer MaxResults + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MaxResults": { + "type": "integer", + "traits": { + "smithy.api#range": { + "min": 0, + "max": 100 + } + } + } + } + } + + +.. _idl-list: + +List shapes +----------- + +A :ref:`list ` shape is defined using a :token:`list_statement`. + +The following example defines a list with a string member from the +:ref:`prelude `: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + list MyList { + member: String + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyList": { + "type": "list", + "member": { + "target": "smithy.api#String" + } + } + } + } + +Traits can be applied to the list shape and its member: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + @length(min: 3, max: 10) + list MyList { + @length(min: 1, max: 100) + member: String + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyList": { + "type": "list", + "member": { + "target": "smithy.api#String", + "traits": { + "smithy.api#length": { + "min": 1, + "max": 100 + } + } + }, + "traits": { + "smithy.api#length": { + "min": 3, + "max": 10 + } + } + } + } + } + + +.. _idl-set: + +Set shapes +---------- + +A :ref:`set ` set shape is defined using a :token:`set_statement`. + +The following example defines a set of strings: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + set StringSet { + member: String + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#StringSet": { + "type": "set", + "member": { + "target": "smithy.api#String" + } + } + } + } + +Traits can be applied to the set shape and its members: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + @deprecated + set StringSet { + @sensitive + member: String + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#StringSet": { + "type": "set", + "member": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#deprecated": {} + } + } + } + } + + +.. _idl-map: + +Map shapes +---------- + +A :ref:`map ` shape is defined using a :token:`map_statement`. + +The following example defines a map of strings to integers: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + map IntegerMap { + key: String, + value: Integer + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "type": "map", + "smithy.example#IntegerMap": { + "key": { + "target": "smithy.api#String" + }, + "value": { + "target": "smithy.api#String" + } + } + } + } + +Traits can be applied to the map shape and its members: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + @length(min: 0, max: 100) + map IntegerMap { + @length(min: 1, max: 10) + key: String, + + @sensitive + value: Integer + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#IntegerMap": { + "type": "map", + "key": { + "target": "smithy.api#String", + "traits": { + "smithy.api#length": { + "min": 1, + "max": 10 + } + } + }, + "value": { + "target": "smithy.api#String", + "traits": { + "smithy.api#sensitive": {} + } + }, + "traits": { + "smithy.api#length": { + "min": 0, + "max": 100 + } + } + } + } + } + + +.. _idl-structure: + +Structure shapes +---------------- + +A :ref:`structure ` shape is defined using a +:token:`structure_statement`. + +The following example defines a structure with two members: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + structure MyStructure { + foo: String, + baz: Integer, + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyStructure": { + "type": "structure", + "members": { + "foo": { + "target": "smithy.api#String" + }, + "baz": { + "target": "smithy.api#Integer" + } + } + } + } + } + +Traits can be applied to structure members: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + /// This is MyStructure. + structure MyStructure { + /// This is documentation for `foo`. + @required + foo: String, + + /// This is documentation for `baz`. + @deprecated + baz: Integer, + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyStructure": { + "type": "structure", + "members": { + "foo": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "This is documentation for `foo`.", + "smithy.api#required": {} + } + }, + "baz": { + "target": "smithy.api#Integer", + "traits": { + "smithy.api#documentation": "This is documentation for `baz`.", + "smithy.api#deprecated": {} + } + } + }, + "traits": { + "smithy.api#documentation": "This is MyStructure." + } + } + } + } + + +.. _idl-union: + +Union shapes +------------ + +A :ref:`union ` shape is defined using a :token:`union_statement`. + +The following example defines a union shape with several members: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + union MyUnion { + i32: Integer, + + stringA: String, + + @sensitive + stringB: String, + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyUnion": { + "type": "union", + "members": { + "i32": { + "target": "smithy.api#Integer" + }, + "stringA": { + "target": "smithy.api#String" + }, + "stringB": { + "target": "smithy.api#String", + "traits": { + "smithy.api#sensitive": {} + } + } + } + } + } + } + + +.. _idl-service: + +Service shape +------------- + +A service shape is defined using a :token:`service_statement` and the provided +:token:`node_object` supports the same properties defined in the +:ref:`service specification `. + +The following example defines a service named ``ModelRepository`` that binds +a resource named ``Model`` and an operation named ``PingService``: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + service ModelRepository { + version: "2020-07-13", + resources: [Model], + operations: [PingService] + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#ModelRepository": { + "type": "service", + "resources": [ + { + "target": "smithy.example#Model" + } + ], + "operations": [ + { + "target": "smithy.example#PingService" + } + ] + } + } + } + + +.. _idl-operation: + +Operation shape +--------------- + +An operation shape is defined using an :token:`operation_statement` and the +provided :token:`node_object` supports the same properties defined in the +:ref:`operation specification `. + +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 ``Unavailable`` or ``BadRequest`` +:ref:`error structures `. + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + operation PingService { + input: Input, + output: Output, + errors: [Unavailable, BadRequest] + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#PingService": { + "type": "operation", + "input": { + "target": "smithy.example#Input" + }, + "output": { + "target": "smithy.example#Output" + }, + "errors": [ + { + "target": "smithy.example#Unavailable" + }, + { + "target": "smithy.example#BadRequest" + } + ] + } + } + } + + +.. _idl-resource: + +Resource shape +-------------- + +A resource shape is defined using a :token:`resource_statement` and the +provided :token:`node_object` supports the same properties defined in the +:ref:`resource specification `. + +The following example defines a resource shape that has a single identifier, +and defines a :ref:`read ` operation: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + resource Model { + identifiers: { + modelId: String, + }, + read: GetModel, + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#Model": { + "type": "resource", + "identifiers": { + "modelId": { + "target": "smithy.api#String" + } + }, + "read": { + "target": "smithy.example#GetModel" + } + } + } + } + + +.. _documentation-comment: + +Documentation comment +===================== + +:token:`Documentation comments ` are a special kind of :token:`comment` that provide +:ref:`documentation ` for shapes. A documentation +comment is formed when three forward slashes (``"///"``) appear as the +first non-whitespace characters on a line. + +Documentation comments are defined using CommonMark_. The text after the +forward slashes is considered the contents of the line. If the text starts +with a space (" "), the leading space is removed from the content. +Successive documentation comments are combined together using a newline +("\\n") to form the documentation of a shape. + +The following Smithy IDL example, + +.. code-block:: smithy + + namespace smithy.example + + /// This is documentation about a shape. + /// + /// - This is a list + /// - More of the list. + string MyString + + /// This is documentation about a trait shape. + /// More docs here. + @trait + structure myTrait {} + +is equivalent to the following JSON AST model: + +.. code-block:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyString": { + "type": "string", + "traits": { + "smithy.api#documentation": "This is documentation about a shape.\n\n- This is a list\n- More of the list." + } + }, + "smithy.example#myTrait": { + "type": "structure", + "traits": { + "smithy.api#trait": {}, + "smithy.api#documentation": "This is documentation about a trait shapes.\n More docs here." + } + } + } + } + +.. rubric:: Placement + +Documentation comments are only treated as shape documentation when the +comment appears immediately before a shape, and documentation comments MUST +appear **before** any :ref:`traits ` applied to the shape in order +for the documentation to be applied to a shape. + +The following example applies a documentation trait to the shape because the +documentation comment comes before the traits applied to a shape: + +.. code-block:: smithy + + /// A deprecated string. + @deprecated + string MyString + +Documentation comments can also be applied to members of a shape. + +.. code-block:: smithy + + /// Documentation about the structure. + structure Example { + /// Documentation about the member. + @sensitive + foo: String, + } + +.. rubric:: Semantic model + +Documentation comments are syntactic sugar equivalent to applying the +:ref:`documentation-trait`, and this difference is inconsequential +in the :ref:`semantic model `. + + +.. _idl-applying-traits: + +Applying traits +=============== + +Trait values immediately preceding a shape definition are applied to the +shape. The shape ID of a trait is *resolved* against :token:`use_statement`\s +and the current namespace in exactly the same same way as +:ref:`other shape IDs `. + +The following example applies the :ref:`sensitive-trait` and +:ref:`documentation-trait` to ``MyString``: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + @sensitive + @documentation("Contains a string") + string MyString + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyString": { + "type": "string", + "traits": { + "smithy.api#documentation": "Contains a string", + "smithy.api#sensitive": {} + } + } + } + } + + +.. _trait-values: + +Trait values +------------ + +The value that can be provided for a trait depends on its type. A value for a +trait is defined by enclosing the value in parenthesis. Trait values can only +appear immediately before a shape. + +The following example applies various traits to a structure shape and its +members. + +.. code-block:: smithy + + @documentation("An animal in the animal kingdom") + structure Animal { + @required + name: smithy.api#String, + + @length(min: 0) + @tags(["internal"]) + age: smithy.api#Integer, + } + + +Structure, map, and union trait values +-------------------------------------- + +Traits that are a ``structure``, ``union``, or ``map`` are defined using +a special syntax that places key-value pairs inside of the trait +parenthesis. Wrapping braces, "{" and "}", are not permitted. + +.. code-block:: smithy + + @structuredTrait(foo: "bar", baz: "bam") + +Nested structure, map, and union values are defined using +:ref:`node value ` productions: + +.. code-block:: smithy + + @structuredTrait( + foo: { + bar: "baz", + qux: "true", + } + ) + +Omitting a value is allowed on ``list``, ``set``, ``map``, and ``structure`` +traits if the shapes have no ``length`` constraints or ``required`` members. +The following applications of the ``foo`` trait are equivalent: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + @trait + structure foo {} + + @foo + string MyString1 + + @foo() + string MyString2 + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#foo": { + "type": "structure", + "traits": { + "smithy.api#trait": {} + } + }, + "smithy.example#MyString1": { + "type": "string", + "traits": { + "smithy.api#foo": {} + } + }, + "smithy.example#MyString2": { + "type": "string", + "traits": { + "smithy.api#foo": {} + } + } + } + } + + +List and set trait values +------------------------- + +Traits that are a ``list`` or ``set`` shape are defined inside +of brackets (``[``) and (``]``) using a :token:`node_array` production. + +.. code-block:: smithy + + @tags(["a", "b"]) + + +Other trait values +------------------ + +All other trait values MUST adhere to the JSON type mappings defined +in :ref:`trait-node-values`. + +The following example defines a string trait value: + +.. code-block:: smithy + + @documentation("Hello") + + +.. _apply-statement: + +Apply statement +--------------- + +Traits can be applied to shapes outside of a shape's definition using an +:token:`apply_statement`. + +The following example applies the :ref:`documentation-trait` and +:ref:`length-trait` to the ``smithy.example#MyString`` shape: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + apply MyString @documentation("This is my string!") + apply MyString @length(min: 1, max: 10) + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyString": { + "type": "apply", + "traits": { + "smithy.api#documentation": "This is my string!", + "smithy.api#length": { + "min": 1, + "max": 10 + } + } + } + } + } + +Traits can be applied to members too: + +.. code-block:: smithy + + namespace smithy.example + + apply MyStructure$foo @documentation("Structure member documentation") + apply MyUnion$foo @documentation("Union member documentation") + apply MyList$member @documentation("List member documentation") + apply MySet$member @documentation("Set member documentation") + apply MyMap$key @documentation("Map key documentation") + apply MyMap$value @documentation("Map key documentation") + +.. seealso:: + + Refer to :ref:`trait conflict resolution ` + for information on how trait conflicts are resolved. + +.. note:: + + In the semantic model, applying traits outside of a shape definition is + treated exactly the same as applying the trait inside of a shape + definition. + + +.. _node-values: + +----------- +Node values +----------- + +*Node values* are analogous to JSON values. Node values are used to define +:ref:`metadata ` and :ref:`trait values `. Smithy's +node values have many advantages over JSON: comments, unquoted keys, unquoted +strings, text blocks, and trailing commas. + +The following example defines a complex object metadata entry using a +node value: + +.. code-block:: smithy + + metadata foo = { + hello: 123, + "foo": "456", + testing: """ + Hello! + """, + an_array: [10.5], + nested-object: { + hello-there$: true + }, // <-- Trailing comma + } + +.. rubric:: Array node + +An array node is defined like a JSON array. A :token:`node_array` contains +zero or more heterogeneous :token:`node_value`\s. A trailing comma is allowed +in a ``node_array``. + +The following examples define arrays with zero, one, and two values: + +* ``[]`` +* ``[true]`` +* ``[1, "hello",]`` + +.. rubric:: Object node + +An object node is defined like a JSON object. A :token:`node_object` contains +zero or more key value pairs of strings (a :token:`node_object_key`) that map +to heterogeneous :token:`node_value`\s. A trailing comma is allowed +in a ``node_object``. + +The following examples define objects with zero, one, and two key value pairs: + +* ``{}`` +* ``{foo: true}`` +* ``{foo: "hello", "bar": [1, 2, {},}`` + +.. rubric:: Number node + +A node :token:`number` contains numeric data. It is defined like a JSON +number. The following examples define several ``number`` values: + +* ``0`` +* ``0.0`` +* ``1234`` +* ``-1234.1234`` +* ``1e+2`` +* ``1.0e-10`` + +.. rubric:: Node keywords + +Several keywords are used when parsing :token:`node_value`. + +* ``true``: The value is treated as a boolean ``true`` +* ``false``: The value is treated as a boolean ``false`` +* ``null``: The value is treated like a JSON ``null`` + + +String values +============= + +A ``node_value`` can contain :token:`node_string_value` productions that all +define strings. + +.. rubric:: New lines + +New lines in strings are normalized from CR (\u000D) and CRLF (\u000D\u000A) +to LF (\u000A). This ensures that strings defined in a Smithy model are +equivalent across platforms. If a literal ``\r`` is desired, it can be added +a string value using the Unicode escape ``\u000d``. + +.. rubric:: String equivalence + +The ``node_string_value`` production defines several productions used to +define strings, and in order for these productions to work in concert with +the :ref:`JSON AST format `, each of these production MUST be +treated like equivalent string values when loaded into the +:ref:`semantic model `. + + +.. _string-escape-characters: + +String escape characters +======================== + +The Smithy IDL supports escape sequences only within quoted strings. The following +escape sequences are allowed: + +.. list-table:: + :header-rows: 1 + :widths: 20 30 50 + + * - Unicode code point + - Escape + - Meaning + * - U+0022 + - ``\"`` + - double quote + * - U+005C + - ``\\`` + - backslash + * - U+002F + - ``\/`` + - forward slash + * - U+0008 + - ``\b`` + - backspace BS + * - U+000C + - ``\f`` + - form feed FF + * - U+000A + - ``\n`` + - line feed LF + * - U+000D + - ``\r`` + - carriage return CR + * - U+0009 + - ``\t`` + - horizontal tab HT + * - U+HHHH + - ``\uHHHH`` + - 4-digit hexadecimal Unicode code point + * - *nothing* + - ``\\r\n``, ``\\r``, ``\\n`` + - escaped new line expands to nothing + +Any other sequence following a backslash is an error. + + +.. _text-blocks: + +Text blocks +=========== + +A text block is a string literal that can span multiple lines and automatically +removes any incidental whitespace. Smithy text blocks are heavily inspired by +text blocks defined in `JEP 355 `_. + +A text block is opened with three double quotes ("""), followed by a newline, +zero or more content characters, and closed with three double quotes. +Text blocks differentiate *incidental whitespace* from *significant whitespace*. +Smithy will re-indent the content of a text block by removing all incidental +whitespace. + +.. code-block:: smithy + + @documentation(""" +
+

Hello!

+
+ """) + +The four leading spaces in the above text block are considered insignificant +because they are common across all lines. Because the closing delimiter +appears on its own line, a trailing new line is added to the result. The +content of the text block is re-indented to remove the insignificant +whitespace, making it equivalent to the following: + +.. code-block:: smithy + + @documentation("
\n

Hello!

\n
\n") + +The closing delimiter can be placed on the same line as content if no new line +is desired at the end of the result. The above example could be rewritten to +not including a trailing new line: + +.. code-block:: smithy + + @documentation(""" +
+

Hello!

+
""") + +This example is equivalent to the following: + +.. code-block:: smithy + + @documentation("
\n

Hello!

\n
") + +The following text blocks are ill-formed: + +.. code-block:: smithy + + """foo""" // missing new line following open delimiter + """ """ // missing new line following open delimiter + """ + " // missing closing delimiter + + +.. _incidental-whitespace: + +Incidental white space removal +------------------------------ + +Smithy will re-indent the content of a text block by removing all +incidental whitespace using the following algorithm: + +1. Split the content of the text block at every LF, producing a list of lines. + The opening LF of the text block is not considered. + + Given the following example ("." is used to represent spaces), + + .. code-block:: smithy + + @documentation(""" + ....Foo + ........Baz + + .. + ....Bar + ....""") + + the following lines are produced: + + .. code-block:: javascript + + [" Foo", " Baz", "", " ", " Bar", " "] + +2. Compute the *common whitespace prefix* by iterating over each line, + counting the number of leading spaces (" ") and taking the minimum count. + Except for the last line of content, lines that are empty or consist wholly + of whitespace are not considered. If the last line of content (that is, the + line that contains the closing delimiter) appears on its own line, then + that line's leading whitespace **is** considered when determining the + common whitespace prefix, allowing the closing delimiter to determine the + amount of indentation to remove. + + Using the previous example, the common whitespace prefix is four spaces. + The empty third line and the blank fourth lines are not considered when + computing the common whitespace. The following uses "." to represent the + common whitespace prefix: + + .. code-block:: smithy + + @documentation(""" + ....Foo + .... Baz + + .... + ....Bar + ....""") + +3. Remove the common white space prefix from each line. + + This step produces the following values from the previous example: + + .. code-block:: javascript + + ["Foo", " Baz", "", "", "Bar", ""] + +4. Remove any trailing spaces from each line. + +5. Concatenate each line together, separated by LF. + + This step produces the following result ("|" is used to represent the + left margin): + + .. code-block:: none + + |Foo + | Baz + | + | + |Bar + | + + +Significant trailing line +------------------------- + +The last line of text block content is used when determining the common +whitespace prefix. + +Consider the following example: + +.. code-block:: smithy + + @documentation(""" + Foo + Baz + Bar + """) + +Because the closing delimiter is at the margin and left of the rest of the +content, the common whitespace prefix is 0 characters, resulting in the +following equivalent string: + +.. code-block:: smithy + + @documentation(" Foo\n Baz\n Bar\n") + +If the closing delimiter is moved to the right of the content, then it has +no bearing on the common whitespace prefix. The common whitespace prefix in +the following example is visualized using "." to represent spaces: + +.. code-block:: smithy + + @documentation(""" + ....Foo + .... Baz + ....Bar + """) + +Because lines are trimmed when they are added to the result, the above example +is equivalent to the following: + +.. code-block:: smithy + + @documentation("Foo\n Baz\nBar\n") + + +Escapes in text blocks +---------------------- + +Text blocks support all of the :ref:`string escape characters ` +of other strings. The use of three double quotes allows unescaped double quotes +(") to appear in text blocks. The following text block is interpreted as +``"hello!"``: + +.. code-block:: smithy + + """ + "hello!" + """ + +Three quotes can appear in a text block without being treated as the closing +delimiter as long as one of the quotes are escaped. The following text block +is interpreted as ``foo """\nbaz``: + +.. code-block:: smithy + + """ + foo \""" + baz""" + +String escapes are interpreted **after** :ref:`incidental whitespace ` +is removed from a text block. The following example uses "." to denote spaces: + +.. code-block:: smithy + + """ + ..
+ ....

Hi\\n....bar

+ ..
+ ..""" + +Because string escapes are expanded after incidental whitespace is removed, it +is interpreted as: + +.. code-block:: none + +
+ ..

Hi + ....bar

+
+ +New lines in the text block can be escaped. This allows for long, single-line +strings to be broken into multiple lines in the IDL. The following example +is interpreted as ``Foo Baz Bam``: + +.. code-block:: smithy + + """ + Foo \ + Baz \ + Bam""" + +Escaped new lines can be intermixed with unescaped newlines. The following +example is interpreted as ``Foo\nBaz Bam``: + +.. code-block:: smithy + + """ + Foo + Baz \ + Bam""" + +.. _ABNF: https://tools.ietf.org/html/rfc5234 +.. _CommonMark: https://spec.commonmark.org/ diff --git a/docs/source/1.0/spec/core/index.rst b/docs/source/1.0/spec/core/index.rst index 8572a1a371b..2aec8f97ee1 100644 --- a/docs/source/1.0/spec/core/index.rst +++ b/docs/source/1.0/spec/core/index.rst @@ -2,17 +2,85 @@ Smithy specification ==================== +This is the specification of Smithy, an interface definition language and set +of tools used to build clients, servers, and other kinds of artifacts through +model transformations. This specification is at version |release|. + + +--------------------------------- +Conventions used in this document +--------------------------------- + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this +document are to be interpreted as described in :rfc:`2119`. + +This specification makes use of the Augmented Backus-Naur Form (ABNF) +:rfc:`5234` notation, including the *core rules* defined in Appendix B +of that document. + +Readers are invited to report technical errors and ambiguities in this +specification to the Smithy GitHub repository at https://github.com/awslabs/smithy. +This specification is open source, so contributions are welcome. + +.. rubric:: Examples + +Unless declared otherwise, example Smithy models given in this specification +are written using the :ref:`Smithy interface definition language (IDL) ` +syntax. Complementary :ref:`JSON AST ` examples are provided +alongside Smithy IDL examples where appropriate. For example: + +.. tabs:: + + .. code-tab:: smithy + + $version: "1.0" + + metadata foo = "bar" + + namespace smithy.example + + use smithy.other.namespace#MyString + + structure MyStructure { + @required + foo: MyString + } + + .. code-tab:: json + + { + "smithy": "1.0", + "metadata": { + "foo": "bar" + }, + "shapes": { + "smithy.example#MyStructure": { + "type": "structure", + "members": { + "foo": { + "target": "smithy.other.namespace#MyString", + "traits": { + "smithy.api#required": {} + } + } + } + } + } + } + +----------------- +Table of contents +----------------- + .. rst-class:: large-toctree .. toctree:: :numbered: :maxdepth: 3 - intro - lexical-structure - shapes + model prelude-model - traits constraint-traits documentation-traits type-refinement-traits @@ -25,7 +93,6 @@ Smithy specification xml-traits endpoint-traits selectors - model-metadata model-validation - merging-models + idl json-ast diff --git a/docs/source/1.0/spec/core/intro.rst b/docs/source/1.0/spec/core/intro.rst deleted file mode 100644 index 4571e7d8945..00000000000 --- a/docs/source/1.0/spec/core/intro.rst +++ /dev/null @@ -1,86 +0,0 @@ -============ -Introduction -============ - -Smithy is an interface definition language and set of tools used to -build clients, servers, and other kinds of artifacts through -model transformations. Smithy models define a service as a collection -of resources, operations, and shapes. - - ----------------------------- -Status of this specification ----------------------------- - -This specification is at version |release|. - - ---------------------- -Requirements notation ---------------------- - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this -document are to be interpreted as described in [:rfc:`2119`]. - -This specification makes use of the Augmented Backus-Naur Form (ABNF) -[:rfc:`5234`] notation, including the *core rules* defined in Appendix B -of that document. - - --------------- -Example models --------------- - -Unless declared otherwise, example Smithy models given in this specification -are written using the :ref:`Smithy Interface Definition Language (IDL) ` -syntax, similar to: - -.. tabs:: - - .. code-tab:: smithy - - structure MyStructure { - @required - foo: String, - - @deprecated - baz: Integer, - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyStructure": { - "type": "structure", - "members": { - "foo": { - "target": "smithy.api#String", - "traits": { - "smithy.api#required": {} - } - }, - "baz": { - "target": "smithy.api#Integer", - "traits": { - "smithy.api#deprecated": {} - } - } - } - } - } - } - -Complementary JSON examples are provided alongside Smithy IDL examples -where appropriate. - - --------- -Feedback --------- - -Readers are invited to report technical errors and ambiguities in this -specification to the Smithy GitHub repository at https://github.com/awslabs/smithy. -This specification is open source, so contributions are welcome. diff --git a/docs/source/1.0/spec/core/json-ast.rst b/docs/source/1.0/spec/core/json-ast.rst index 6e5af3427bc..167fb374484 100644 --- a/docs/source/1.0/spec/core/json-ast.rst +++ b/docs/source/1.0/spec/core/json-ast.rst @@ -11,8 +11,6 @@ parser. * Smithy JSON models can be merged together with other JSON models or other Smithy IDL models using the rules defined in :ref:`merging-models`. -* Unless specified otherwise, the same constraints and logic is used to load - JSON models that is used to load Smithy IDL models. * All shape IDs in the JSON AST MUST be absolute shape IDs that contain a namespace. One of the main drivers of the simplicity of the the JSON AST over the Smithy IDL is that relative and forward references never need to @@ -165,7 +163,7 @@ example defines a shape for each simple type: List and set shapes ------------------- -:ref:`list` and :ref:`set` shapes have a required ``member`` property +:ref:`list` and :ref:`set ` shapes have a required ``member`` property that is an :ref:`AST member `. The following example defines a list with a string member: @@ -618,14 +616,16 @@ The following example defines an operation, its input, output, and errors: } +.. _ast-apply: + -------------- AST apply type -------------- Traits can be applied to shapes outside of their definition by setting ``type`` to ``apply``. The ``apply`` type does not actually define a shape -for the shape ID; the shape ID MUST reference a shape or member of a shape. -The ``apply`` type allows only the ``traits`` property. +for the shape ID; the shape ID MUST reference a shape to which traits are +applied. The ``apply`` type allows only the ``traits`` property. .. code-block:: json diff --git a/docs/source/1.0/spec/core/lexical-structure.rst b/docs/source/1.0/spec/core/lexical-structure.rst deleted file mode 100644 index 3bca72b90a3..00000000000 --- a/docs/source/1.0/spec/core/lexical-structure.rst +++ /dev/null @@ -1,910 +0,0 @@ -.. _lexical-structure: - -============================ -Smithy IDL lexical structure -============================ - -Smithy models are defined using either the Smithy interface definition language -(IDL) or the :ref:`JSON abstract syntax tree ` (AST). This document -defines the ABNF_ grammar and syntax for defining models with the Smithy IDL. - -.. contents:: Table of contents - :depth: 2 - :local: - :backlinks: none - - -.. _semantic-model: - --------------- -Semantic model --------------- - -Smithy's *semantic model* is a higher-level abstraction than the IDL or -JSON AST. It provides a map of :ref:`model metadata ` and a map of -absolute :ref:`shape IDs ` to :ref:`shapes `. In this sense, -the JSON AST format much more closely resembles the semantic model than the -IDL (with the primary exceptions being :ref:`apply statements ` -are inlined directly inside shape definitions and all of the files that define -the model are :ref:`merged together `). - -The IDL is essentially a specialized syntax that makes it easier to read -and author the equivalent JSON AST. Higher-level syntactic features of the -IDL that are not present in the AST are not part of the semantic model, and -these IDL features SHOULD be transformed into the JSON AST equivalent when -parsing the IDL to populate the semantic model. - - -.. _smithy-idl-abnf: - ---------------- -Smithy IDL ABNF ---------------- - -The Smithy IDL is defined by the following ABNF: - -.. productionlist:: smithy - idl:`ws` `control_section` `metadata_section` `shape_section` - - -------------- -Lexical notes -------------- - -Smithy models MUST be encoded using UTF-8 and SHOULD use Unix style -line endings (``\n``). The Smithy ABNF is whitespace sensitive. -Whitespace is controlled using the following productions: - -.. productionlist:: smithy - ws :*(`sp` / `newline` / `line_comment`) ; whitespace - sp :*(%x20 / %x09) ; " " and \t - br :`sp` (`line_comment` / `newline`) `sp` ; break - newline :%x0A / %x0D.0A ; \n and \r\n - - -.. _comments: - -Comments -======== - -Comments can occur at any place in the IDL between tokens where whitespace -(:token:`ws`) can occur. Comments in Smithy are defined using two forward -slashes followed by any character. A newline terminates a comment. - -.. productionlist:: smithy - line_comment: "//" *`not_newline` `newline` - not_newline: %x09 / %x20-10FFFF ; Any character except newline - -.. code-block:: smithy - :caption: Example - - // This is a comment - namespace com.foo // This is also a comment - - // Another comment - string MyString - - -.. _documentation-comment: - -Documentation comment ---------------------- - -Documentation comments are a special kind of comment that provide -:ref:`documentation ` for shapes. A documentation -comment is formed when three forward slashes (``"///"``) appear as the -first non-whitespace characters on a line. - -.. productionlist:: smithy - documentation_comment:"///" *`not_newline` `br` - -Documentation comments are defined using CommonMark_. The text after the -forward slashes is considered the contents of the line. If the text starts -with a space (" "), the leading space is removed from the content. -Successive documentation comments are combined together using a newline -("\\n") to form the documentation of a shape. - -The following Smithy IDL example, - -.. code-block:: smithy - - namespace smithy.example - - /// This is documentation about a shape. - /// - /// - This is a list - /// - More of the list. - string MyString - - /// This is documentation about a trait shape. - /// More docs here. - @trait - structure myTrait {} - -is equivalent to the following JSON AST model: - -.. code-block:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyString": { - "type": "string", - "traits": { - "smithy.api#documentation": "This is documentation about a shape.\n\n- This is a list\n- More of the list." - } - }, - "smithy.example#myTrait": { - "type": "structure", - "traits": { - "smithy.api#trait": {}, - "smithy.api#documentation": "This is documentation about a trait shapes.\n More docs here." - } - } - } - } - -.. rubric:: Placement - -Documentation comments are only treated as shape documentation when the -comment appears immediately before a shape, and documentation comments MUST -appear **before** any :ref:`traits ` applied to the shape in order -for the documentation to be applied to a shape. - -The following example applies a documentation trait to the shape because the -documentation comment comes before the traits applied to a shape: - -.. code-block:: smithy - - /// A deprecated string. - @deprecated - string MyString - -Documentation comments can also be applied to members of a shape. - -.. code-block:: smithy - - /// Documentation about the structure. - structure Example { - /// Documentation about the member. - @sensitive - foo: String, - } - -.. rubric:: Semantic model - -Documentation comments are syntactic sugar equivalent to applying the -:ref:`documentation-trait`, and this difference is inconsequential -in the :ref:`semantic model `. - - -.. _control-statement: - ---------------- -Control section ---------------- - -The *control section* of a model contains :token:`control statements ` -that apply parser directives to a *specific IDL file*. Because control -statements influence parsing, they MUST appear at the beginning of a file -before any other statements. - -.. productionlist:: smithy - control_section :*(`control_statement`) - control_statement :"$" `ws` `node_object_key` `ws` ":" `ws` `node_value` `br` - -The :ref:`version ` statement is currently the only control -statement defined in the Smithy IDL. Implementations MUST ignore unknown -control statements. - -.. rubric:: Semantic model - -Control statements are not part of the :ref:`semantic model `. - - -.. _smithy-version: - -Version statement -================= - -The Smithy specification is versioned using a ``major`` . ``minor`` -versioning scheme. A version requirement is specified for a model file using -the ``$version`` control statement. When no version number is specified in -the IDL, an implementation SHOULD assume that the model can be loaded. -Because this can lead to unexpected parsing errors, models SHOULD always -include a version. - -The value provided in a version control statement is a string that MUST -adhere to the following ABNF: - -.. productionlist:: smithy - version_string :1*DIGIT [ "." 1*DIGIT ] - -The following example sets the version to ``1``, meaning that tooling MUST -support a version greater than or equal to ``1.0`` and less than ``2.0``: - -.. tabs:: - - .. code-tab:: smithy - - $version: "1" - - .. code-tab:: json - - { - "smithy": "1" - } - -A minor version SHOULD be provided when a model depends on a feature released -in a minor update of the specification. The following example sets the -version requirement of a file to ``1.1``, meaning that tooling MUST support a -version greater than or equal to ``1.1`` and less than ``2.0``: - -.. tabs:: - - .. code-tab:: smithy - - $version: "1.1" - - .. code-tab:: json - - { - "smithy": "1.1" - } - -.. rubric:: Version compatibility - -A single version statement can appear in a model file, but different versions -MAY be encountered when merging multiple model files together. Multiple -versions are supported if and only if all of the version statements are -supported by the tool loading the models. - - -.. _shape-id: - --------- -Shape ID --------- - -A :dfn:`shape ID` is used to refer to shapes and traits in the model. -Shape IDs adhere to the following syntax: - -.. code-block:: none - - com.foo.baz#ShapeName$memberName - \_________/ \_______/ \________/ - | | | - Namespace Shape name Member name - -Absolute shape ID - An :dfn:`absolute shape ID` starts with a :token:`namespace` name, - followed by "``#``", followed by a *relative shape ID*. -Relative shape ID - A :dfn:`relative shape ID` contains a :token:`shape name ` - and an optional :token:`member name `. The shape name and - member name are separated by the "``$``" symbol if a member name is - present. - - -.. _shape-id-abnf: - -Shape ID ABNF -============= - -Shape IDs are formally defined by the following ABNF: - -.. productionlist:: smithy - shape_id :`root_shape_id` [`shape_id_member`] - root_shape_id :`absolute_root_shape_id` / `identifier` - absolute_root_shape_id :`namespace` "#" `identifier` - namespace :`identifier` *("." `identifier`) - identifier :(ALPHA / "_") *(ALPHA / DIGIT / "_") - shape_id_member :"$" `identifier` - - -.. _shape-id-member-names: - -Shape ID member names -===================== - -A :ref:`member ` of an :ref:`aggregate shape ` can be -referenced in a shape ID by appending a dollar sign (``$``) followed by the -appropriate member name. Member names for each shape are defined as follows: - -.. list-table:: - :header-rows: 1 - :widths: 25 40 35 - - * - Shape ID - - Syntax - - Example - * - :ref:`structure` member - - ``$`` - - ``ns.example#Shape$baz`` - * - :ref:`union` member - - ``$`` - - ``ns.example#Shape$baz`` - * - :ref:`list` member - - ``$member`` - - ``ns.example#Shape$member`` - * - :ref:`set` member - - ``$member`` - - ``ns.example#Shape$member`` - * - :ref:`map` key - - ``$key`` - - ``ns.example#Shape$key`` - * - :ref:`map` value - - ``$value`` - - ``ns.example#Shape$value`` - - -.. _shape-id-conflicts: - -Shape ID conflicts -================== - -While shape IDs used within a model are case-sensitive, no two shapes in -the model can have the same case-insensitive shape ID. For example, -``com.Foo#baz`` and ``com.foo#baz`` are not allowed in the same model. This -property also extends to member names: ``com.foo#Baz$bar`` and -``com.foo#Baz$Bar`` are not allowed on the same structure. - - -.. _syntactic-shape-ids: - -Syntactic shape IDs in the IDL -============================== - -Unquoted string values that are not object keys in the Smithy IDL are -considered lexical shape IDs and are resolved to absolute shape IDs using the -process defined in :ref:`relative-shape-id`. - -The following model defines a list that references a string shape defined -in another namespace. - -.. code-block:: smithy - - namespace smithy.example - - use smithy.other#MyString - - list MyList { - member: MyString - } - -The above model is equivalent to the following JSON AST model: - -.. code-block:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyList": { - "type": "list", - "members": { - "target": "smithy.other#MyString" - } - } - } - } - -.. rubric:: Use quotes for literal strings - -Values in the IDL that are not meant to be shape IDs MUST be quoted. The -following model is syntactically valid but semantically incorrect because -it resolves the value of the :ref:`error-trait` to the shape ID -``"smithy.example#client"`` rather than using the string literal value of -``"client"``: - -.. code-block:: smithy - - namespace smithy.example - - @error(client) // <-- This MUST be "client" - structure Error - - string client - -The above example is equivalent to the following incorrect JSON AST: - -.. code-block:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#Error": { - "type": "structure", - "traits": { - "smithy.api#error": "smithy.example#client" - } - }, - "smithy.example#client": { - "type": "string" - } - } - } - -.. rubric:: Object keys - -Object keys in the IDL are not treated as shape IDs. The following example -defines a :ref:`metadata ` object (arbitrary information about -the model), and when loaded into the :ref:`semantic model `, -the object key ``String`` remains the same literal string value of -``String`` while the value is treated as a shape ID and resolves to the -string literal ``"smithy.api#String"``. - -.. code-block:: smithy - - metadata foo = { - String: String, - } - -The above example is equivalent to the following JSON AST: - -.. code-block:: json - - { - "smithy": "1.0", - "metadata": { - "String": "smithy.api#String" - } - } - -.. rubric:: Semantic model - -Syntactic shape IDs in the IDL are syntactic sugar for defining -fully-qualified shape IDs inside of strings, and this difference -is inconsequential in the :ref:`semantic model `. -A syntactic shape ID SHOULD be resolved to a string that contains a -fully-qualified shape ID when parsing the model. The difference -between a string and shape ID MUST NOT be a concern that traits -need to worry about handling when they are loaded nor is this -difference exposed in other parts of the specification like -:ref:`selectors `. - - -.. _node-values: - ------------ -Node values ------------ - -*Node values* are analogous to JSON values. Node values are used to define -:ref:`metadata ` and :ref:`trait values `. -Smithy's node values have many advantages over JSON: comments, -unquoted keys, unquoted strings, single quoted strings, long strings, -and trailing commas. - -.. productionlist:: smithy - node_value :`node_array` - :/ `node_object` - :/ `number` - :/ `node_keywords` - :/ `node_string_value` - -.. rubric:: Array node - -.. productionlist:: smithy - node_array :`empty_node_array` / `populated_node_array` - empty_node_array :"[" `ws` "]" - populated_node_array:"[" `ws` `node_value` `ws` - : *(`comma` `node_value` `ws`) - : `trailing_comma` "]" - trailing_comma :[`comma`] - comma :"," `ws` - -.. rubric:: Object node - -.. productionlist:: smithy - node_object :`empty_node_object` / `populated_node_object` - empty_node_object :"{" `ws` "}" - populated_node_object:"{" `ws` `node_object_kvp` `ws` - : *(`comma` `node_object_kvp` `ws`) - : `trailing_comma` "}" - node_object_kvp :`node_object_key` `ws` ":" `ws` `node_value` - node_object_key :`quoted_text` / `identifier` - -.. rubric:: Number node - -.. productionlist:: smithy - number :[`minus`] `int` [`frac`] [`exp`] - decimal_point :%x2E ; . - digit1_9 :%x31-39 ; 1-9 - e :%x65 / %x45 ; e E - exp :`e` [`minus` / `plus`] 1*DIGIT - frac :`decimal_point` 1*DIGIT - int :`zero` / (`digit1_9` *DIGIT) - minus :%x2D ; - - plus :%x2B ; + - zero :%x30 ; 0 - -.. rubric:: Node keywords - -Several keywords are used when parsing :token:`node_value`. - -.. productionlist:: smithy - node_keywords: "true" / "false" / "null" - -* ``true``: The value is treated as a boolean ``true`` -* ``false``: The value is treated as a boolean ``false`` -* ``null``: The value is treated like a JSON ``null`` - -.. rubric:: Node examples - -The following examples apply metadata to a model to demonstrate how node -values are defined. - -The following example defines a string metadata entry: - -.. code-block:: smithy - - metadata foo = "baz" - -The following example defines an integer metadata entry: - -.. code-block:: smithy - - metadata foo = 100 - -The following example defines an array metadata entry: - -.. code-block:: smithy - - metadata foo = ["hello", 123, true, [false]] - -The following example defines a complex object metadata entry: - -.. code-block:: smithy - - metadata foo = { - hello: 123, - 'foo': "456", - testing: """ - Hello! - """, - an_array: [10.5], - nested-object: { - hello-there$: true - }, // <-- Trailing comma - } - - -------------- -String values -------------- - -A ``node_value`` can contain ``node_string_value`` productions that all -define strings. - -.. productionlist:: smithy - node_string_value :`shape_id` / `text_block` / `quoted_text` - quoted_text :DQUOTE *`quoted_char` DQUOTE - quoted_char :%x20-21 ; space - "!" - :/ %x23-5B ; "#" - "[" - :/ %x5D-10FFFF ; "]"+ - :/ `escaped_char` - :/ `preserved_double` - escaped_char :`escape` (`escape` / "'" / DQUOTE / "b" / "f" / "n" / "r" / "t" / "/" / `unicode_escape`) - unicode_escape :"u" `hex` `hex` `hex` `hex` - hex : DIGIT / %x41-46 / %x61-66 - preserved_double :`escape` (%x20-21 / %x23-5B / %x5D-10FFFF) - escape :%x5C ; backslash - text_block :`three_dquotes` `br` *`quoted_char` `three_dquotes` - three_dquotes :DQUOTE DQUOTE DQUOTE - -.. rubric:: New lines - -New lines in strings are normalized from CR (\u000D) and CRLF (\u000D\u000A) -to LF (\u000A). This ensures that strings defined in a Smithy model are -equivalent across platforms. If a literal ``\r`` is desired, it can be added -a string value using the Unicode escape ``\u000d``. - -.. rubric:: String equivalence - -The ``node_string_value`` production defines several productions used to -define strings, and in order for these productions to work in concert with -the :ref:`JSON AST format `, each of these production MUST be -treated like equivalent string values when loaded into the -:ref:`semantic model `. - - -.. _string-escape-characters: - -String escape characters -======================== - -The Smithy IDL supports escape sequences only within quoted strings. The following -escape sequences are allowed: - -.. list-table:: - :header-rows: 1 - :widths: 20 30 50 - - * - Unicode code point - - Escape - - Meaning - * - U+0022 - - ``\"`` - - double quote - * - U+005C - - ``\\`` - - backslash - * - U+002F - - ``\/`` - - forward slash - * - U+0008 - - ``\b`` - - backspace BS - * - U+000C - - ``\f`` - - form feed FF - * - U+000A - - ``\n`` - - line feed LF - * - U+000D - - ``\r`` - - carriage return CR - * - U+0009 - - ``\t`` - - horizontal tab HT - * - U+HHHH - - ``\uHHHH`` - - 4-digit hexadecimal Unicode code point - * - *nothing* - - ``\\r\n``, ``\\r``, ``\\n`` - - escaped new line expands to nothing - -Any other sequence following a backslash is an error. - - -.. _text-blocks: - -Text blocks -=========== - -A text block is a string literal that can span multiple lines and automatically -removes any incidental whitespace. Smithy text blocks are heavily inspired by -text blocks defined in `JEP 355 `_. - -A text block is opened with three double quotes ("""), followed by a newline, -zero or more content characters, and closed with three double quotes. -Text blocks differentiate *incidental whitespace* from *significant whitespace*. -Smithy will re-indent the content of a text block by removing all incidental -whitespace. - -.. code-block:: smithy - - @documentation(""" -
-

Hello!

-
- """) - -The four leading spaces in the above text block are considered insignificant -because they are common across all lines. Because the closing delimiter -appears on its own line, a trailing new line is added to the result. The -content of the text block is re-indented to remove the insignificant -whitespace, making it equivalent to the following: - -.. code-block:: smithy - - @documentation("
\n

Hello!

\n
\n") - -The closing delimiter can be placed on the same line as content if no new line -is desired at the end of the result. The above example could be rewritten to -not including a trailing new line: - -.. code-block:: smithy - - @documentation(""" -
-

Hello!

-
""") - -This example is equivalent to the following: - -.. code-block:: smithy - - @documentation("
\n

Hello!

\n
") - -The following text blocks are ill-formed: - -.. code-block:: smithy - - """foo""" // missing new line following open delimiter - """ """ // missing new line following open delimiter - """ - " // missing closing delimiter - - -.. _incidental-whitespace: - -Incidental white space removal ------------------------------- - -Smithy will re-indent the content of a text block by removing all -incidental whitespace using the following algorithm: - -1. Split the content of the text block at every LF, producing a list of lines. - The opening LF of the text block is not considered. - - Given the following example ("." is used to represent spaces), - - .. code-block:: smithy - - @documentation(""" - ....Foo - ........Baz - - .. - ....Bar - ....""") - - the following lines are produced: - - .. code-block:: javascript - - [" Foo", " Baz", "", " ", " Bar", " "] - -2. Compute the *common whitespace prefix* by iterating over each line, - counting the number of leading spaces (" ") and taking the minimum count. - Except for the last line of content, lines that are empty or consist wholly - of whitespace are not considered. If the last line of content (that is, the - line that contains the closing delimiter) appears on its own line, then - that line's leading whitespace **is** considered when determining the - common whitespace prefix, allowing the closing delimiter to determine the - amount of indentation to remove. - - Using the previous example, the common whitespace prefix is four spaces. - The empty third line and the blank fourth lines are not considered when - computing the common whitespace. The following uses "." to represent the - common whitespace prefix: - - .. code-block:: smithy - - @documentation(""" - ....Foo - .... Baz - - .... - ....Bar - ....""") - -3. Remove the common white space prefix from each line. - - This step produces the following values from the previous example: - - .. code-block:: javascript - - ["Foo", " Baz", "", "", "Bar", ""] - -4. Remove any trailing spaces from each line. - -5. Concatenate each line together, separated by LF. - - This step produces the following result ("|" is used to represent the - left margin): - - .. code-block:: none - - |Foo - | Baz - | - | - |Bar - | - - -Significant trailing line -------------------------- - -The last line of text block content is used when determining the common -whitespace prefix. - -Consider the following example: - -.. code-block:: smithy - - @documentation(""" - Foo - Baz - Bar - """) - -Because the closing delimiter is at the margin and left of the rest of the -content, the common whitespace prefix is 0 characters, resulting in the -following equivalent string: - -.. code-block:: smithy - - @documentation(" Foo\n Baz\n Bar\n") - -If the closing delimiter is moved to the right of the content, then it has -no bearing on the common whitespace prefix. The common whitespace prefix in -the following example is visualized using "." to represent spaces: - -.. code-block:: smithy - - @documentation(""" - ....Foo - .... Baz - ....Bar - """) - -Because lines are trimmed when they are added to the result, the above example -is equivalent to the following: - -.. code-block:: smithy - - @documentation("Foo\n Baz\nBar\n") - - -Escapes in text blocks ----------------------- - -Text blocks support all of the :ref:`string escape characters ` -of other strings. The use of three double quotes allows unescaped double quotes -(") to appear in text blocks. The following text block is interpreted as -``"hello!"``: - -.. code-block:: smithy - - """ - "hello!" - """ - -Three quotes can appear in a text block without being treated as the closing -delimiter as long as one of the quotes are escaped. The following text block -is interpreted as ``foo """\nbaz``: - -.. code-block:: smithy - - """ - foo \""" - baz""" - -String escapes are interpreted **after** :ref:`incidental whitespace ` -is removed from a text block. The following example uses "." to denote spaces: - -.. code-block:: smithy - - """ - ..
- ....

Hi\\n....bar

- ..
- ..""" - -Because string escapes are expanded after incidental whitespace is removed, it -is interpreted as: - -.. code-block:: none - -
- ..

Hi - ....bar

-
- -New lines in the text block can be escaped. This allows for long, single-line -strings to be broken into multiple lines in the IDL. The following example -is interpreted as ``Foo Baz Bam``: - -.. code-block:: smithy - - """ - Foo \ - Baz \ - Bam""" - -Escaped new lines can be intermixed with unescaped newlines. The following -example is interpreted as ``Foo\nBaz Bam``: - -.. code-block:: smithy - - """ - Foo - Baz \ - Bam""" - - -.. _ABNF: https://tools.ietf.org/html/rfc5234 -.. _CommonMark: https://spec.commonmark.org/ diff --git a/docs/source/1.0/spec/core/merging-models.rst b/docs/source/1.0/spec/core/merging-models.rst deleted file mode 100644 index 8ccd62a7e96..00000000000 --- a/docs/source/1.0/spec/core/merging-models.rst +++ /dev/null @@ -1,18 +0,0 @@ -.. _merging-models: - -============== -Merging models -============== - -Smithy models MAY be divided into multiple files so that they are easier to -maintain and evolve. Smithy tools MUST take the following steps to merge two -models together to form a composite model: - -#. Assert that both models use a :ref:`version ` that is - compatible with the tool versions specified. -#. Duplicate shape names, if found, MUST cause the model merge to fail. - See :ref:`shape-id-conflicts` for more information. -#. Merge any conflicting :ref:`trait ` definitions using - :ref:`trait conflict resolution `. -#. Merge the :ref:`metadata ` properties of both models using the - :ref:`metadata merge rules `. diff --git a/docs/source/1.0/spec/core/model-metadata.rst b/docs/source/1.0/spec/core/model-metadata.rst deleted file mode 100644 index 3b872669fe6..00000000000 --- a/docs/source/1.0/spec/core/model-metadata.rst +++ /dev/null @@ -1,88 +0,0 @@ -.. _metadata: - -============== -Model metadata -============== - -:dfn:`Metadata` is a schema-less extensibility mechanism that can be applied -to a model using a :ref:`metadata statement `. For -example, metadata is used to define :ref:`validators ` and -:ref:`suppressions ` that are applied to the entire -model. - -.. contents:: Table of contents - :depth: 1 - :local: - :backlinks: none - - -.. _metadata-statement: - ------------------- -Metadata statement ------------------- - -Metadata statements MUST appear before any namespace statement or any shapes -are defined. Metadata is defined by the following ABNF: - -.. productionlist:: smithy - metadata_section :*(`metadata_statement`) - metadata_statement :"metadata" `ws` `node_object_key` `ws` "=" `ws` `node_value` `br` - -.. code-block:: smithy - :caption: Example - - metadata exampleString = "hello there" - metadata "example.string2" = 'hello there' - metadata bool1 = true - metadata bool2 = false - metadata number = 10 - metadata array = [10, true, "hello"] - metadata object = {foo: "baz"} - metadata null = null - - -.. _merging-metadata: - ----------------- -Merging metadata ----------------- - -When a conflict occurs between top-level metadata key-value pairs, -metadata is merged using the following logic: - -1. If a metadata key is only present in one model, then the entry is valid - and added to the merged model. -2. If both models contain the same key and both values are arrays, then - the entry is valid; the values of both arrays are concatenated into a - single array and added to the merged model. -3. If both models contain the same key and both values are exactly equal, - then the conflict is ignored and the value is added to the merged model. -4. If both models contain the same key and the values do not both map to - arrays, then the key is invalid and there is a metadata conflict error. - -Given the following two Smithy models: - -.. code-block:: smithy - :caption: model-a.smithy - - metadata "foo" = ["baz", "bar"] - metadata "qux" = "test" - metadata "validConflict" = "hi!" - -.. code-block:: smithy - :caption: model-b.smithy - - metadata "foo" = ["lorem", "ipsum"] - metadata "lorem" = "ipsum" - metadata "validConflict" = "hi!" - -Merging ``model-a.smithy`` and ``model-b.smithy`` produces the following -model: - -.. code-block:: smithy - - metadata "foo" = ["baz", "bar", "lorem", "ipsum"] - metadata "qux" = "test" - metadata "lorem" = "ipsum" - metadata "validConflict" = "hi!" diff --git a/docs/source/1.0/spec/core/model.rst b/docs/source/1.0/spec/core/model.rst new file mode 100644 index 00000000000..82f62f953bc --- /dev/null +++ b/docs/source/1.0/spec/core/model.rst @@ -0,0 +1,2631 @@ +.. _smithy-model: + +================ +The Smithy model +================ + +The *Smithy model* describes the Smithy semantic model and the files used to +create it. Smithy models are used to describe services and data structures. + +.. contents:: Table of contents + :depth: 1 + :local: + :backlinks: none + + +.. _smithy-overview: + +---------------- +Smithy framework +---------------- + +Smithy is a framework that consists of a semantic model, file formats used to +define a model, and a build process used to validate models and facilitate +model transformations. + +.. text-figure:: + :caption: **Figure 1.1**: Smithy framework concepts + :name: figure-1.1 + + ┌────────────────┐ part of ┌────────────────┐ + │ │╲ ╱│ │ + │ Semantic Model │─○──────────────○─│ Model File │ + │ │╱ ╲│ │ + └────────────────┘ └────────────────┘ + split into ╲│╱ + ○ + │ + ┌────────────────┐ ┼ + │JSON AST (.json)│────────┐ ┌────────────────┐ + └────────────────┘ │ │ │ + ├────────▷│ Representation │ + ┌────────────────┐ │ │ │ + │ IDL (.smithy) │────────┘ └────────────────┘ + └────────────────┘ + +Semantic model + The in-memory model used by tools. The :ref:`semantic model ` + may be serialized into one or more model file representations. +Model File + A file on the file system, in a particular representation. The model files + that make up a semantic model MAY be split across multiple files to + improve readability or modularity, and those files are not required to + use the same representation. +Representation + A particular model file format such as the Smithy IDL or JSON AST. + Representations are loaded into the semantic model by mapping the + representation to concepts in the semantic model. + + * The :ref:`Smithy IDL ` is a human-readable format that aims to + streamline authoring and reading models. + * The :ref:`JSON AST ` aims to provide a more machine-readable + format to easily share models across language implementations and better + integrate with JSON-based ecosystems. + + +.. _semantic-model: + +------------------ +The semantic model +------------------ + +Smithy's *semantic model* is an in-memory model used by tools. It is +independent of any particular serialized representation. The semantic +model contains :ref:`metadata ` and a graph of +:ref:`shapes ` connected by :ref:`shape IDs `. + +.. text-figure:: + :caption: **Figure 1.3**: The semantic model + :name: figure-1.3 + + ┌───────────────┐ + │Semantic Model │╲ + ├───────────────┤─○────────┐ + │metadata? │╱ │ + │ │ │ + │ │ │ + └───────────────┘ │ + ┼ ┼ prelude │ + │ ○────────────┘ + ○ + shapes╱│╲ + ┌───────────────┐ ┌───────────────┐ + │ Applied Trait │╲ shape │ «abstract» │ + ├───────────────┤─○──────────────┼│ Shape │ ┌───────────────┐ + │ │╱ ├───────────────┤ │ ShapeID │ + │ │ │ │ ├───────────────┤ + │ │╲ applied-to │ │ id │namespace │ + │ │─○──────────────┼│ │┼──────────┼│shape_name │ + │ │╱traits │ │ │member_name? │ + └───────────────┘ └───────────────┘ └───────────────┘ + +Shape + Shapes are named data definitions that describe the structure of an API. + Shapes are referenced and connected by :ref:`shape IDs `. + Relationships between shapes are formed by :ref:`members ` that + target other shapes, properties of shapes like the ``input`` and + ``output`` properties of an :ref:`operation `, and + :ref:`applied traits ` that attach a trait to a shape. +Shape ID + A :ref:`shape ID ` is used to identify shapes defined in a + model. For example, ``smithy.example#MyShape``, + ``smithy.example#Foo$bar``, and ``Baz`` are all different kinds of shape + IDs. +Trait + :ref:`Traits ` are specialized shapes that form the basis of + Smithy's meta-model. Traits are applied to shapes to associate metadata + to a shape. They are typically used by tools to influence validation, + serialization, and code generation. +Applied trait + An applied trait is an instance of a trait applied to a shape, configured + using a :ref:`node value `. +Model metadata + :ref:`Metadata ` is a schema-less extensibility mechanism used + to associate metadata to an entire model. +Prelude + The :ref:`prelude ` defines various simple shapes and every + trait defined in the core specification. All Smithy models automatically + include the prelude. + + +.. _model-files: + +----------- +Model files +----------- + +Smithy models MAY be divided into multiple files so that they are easier to +maintain and evolve. One or more model files can be assembled (or merged) +together to form a semantic model. The model files that form a semantic model +are not required to all be defined in the same representation; some models can +be defined using the IDL and others can be defined using the JSON AST. + +Model files do not explicitly include other model files; this responsibility +is left to tooling to ensure that all necessary model files are merged +together to form a valid semantic model. + +.. _merging-models: + +Merging model files +=================== + +Implementations MUST take the following steps to merge models together to load +the semantic model: + +#. Duplicate shape IDs, if found, MUST cause the model merge to fail. + See :ref:`shape-id-conflicts` for more information. +#. Merge any conflicting applied traits using + :ref:`trait conflict resolution `. +#. Merge the metadata objects of both models using the steps defined + in :ref:`merging-metadata`. + + +.. _metadata: + +-------- +Metadata +-------- + +Metadata is a schema-less extensibility mechanism used to associate +metadata to an entire model. For example, metadata is used to define +:ref:`validators ` and model-wide +:ref:`suppressions `. Metadata is defined +using an ``object`` :ref:`node value `. + + +.. _merging-metadata: + +Merging metadata +================ + +When a conflict occurs between top-level metadata key-value pairs, +metadata is merged using the following logic: + +1. If a metadata key is only present in one model, then the entry is valid + and added to the merged model. +2. If both models contain the same key and both values are arrays, then + the entry is valid; the values of both arrays are concatenated into a + single array and added to the merged model. +3. If both models contain the same key and both values are exactly equal, + then the conflict is ignored and the value is added to the merged model. +4. If both models contain the same key, the values do not both map to + arrays, and the values are not equal, then the key is invalid and there + is a metadata conflict error. + +Given the following two Smithy models: + +.. code-block:: smithy + :caption: model-a.smithy + + metadata "foo" = ["baz", "bar"] + metadata "qux" = "test" + metadata "validConflict" = "hi!" + +.. code-block:: smithy + :caption: model-b.smithy + + metadata "foo" = ["lorem", "ipsum"] + metadata "lorem" = "ipsum" + metadata "validConflict" = "hi!" + +Merging ``model-a.smithy`` and ``model-b.smithy`` produces the following +model: + +.. code-block:: smithy + + metadata "foo" = ["baz", "bar", "lorem", "ipsum"] + metadata "qux" = "test" + metadata "lorem" = "ipsum" + metadata "validConflict" = "hi!" + + +.. _node-value: + +----------- +Node values +----------- + +Node values are JSON-like values used in the following places in the +semantic model: + +* **metadata**: Metadata is defined as a node value object. +* **applied trait**: The value of a trait applied to a shape is defined + using a node value. + +.. text-figure:: + :caption: **Figure 1.4**: Node value types + :name: figure-1.4 + + ┌─────────────────┐ ┌─────────────┐ + │ Semantic Model │ │Applied Trait│ + └─────────────────┘ └─────────────┘ + │ │ + │ │ + │ ┼ nodeValue + │ ┌─────────────┐ + │ │ «abstract» │ + │ │ Value │ + │metadata └─────────────┘ + │ △ + ○ ┌───────────────────┬─────────────────┼───────────────┬───────────────┐ + ┼ │ │ │ │ │ + ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ + │ Object │ │ Array │ │ Number │ │ Boolean │ │ String │ + ├─────────────────┤ ├─────────────────┤ └─────────────┘ └─────────────┘ └─────────────┘ + │members: │ │members: [Value] │ + │ [String, Value]│ └─────────────────┘ + └─────────────────┘ + +The following example defines metadata using a node value: + +.. tabs:: + + .. code-tab:: smithy + + metadata foo = "hello" + + .. code-tab:: json + + { + "smithy": "1.0", + "metadata": { + "foo": "hello" + } + } + +The following example defines a trait using a node value: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + @length(min: 1, max: 10) + string MyString + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyString": { + "type": "string", + "traits": { + "smithy.api#length": { + "min": 1, + "max": 10 + } + } + } + } + } + + +Node value types +================ + +Node values have the same data model as JSON; they consist of the following +kinds of values: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Type + - Description + * - null + - The lack of a value + * - string + - A UTF-8 string + * - number + - A double precision floating point number + * - boolean + - A Boolean, true or false value + * - array + - A list of heterogeneous node values + * - object + - A map of string keys to heterogeneous node values + +.. rubric:: Shape IDs, text blocks, et al. + +There is no specific node value type for shape IDs, text blocks, or +other higher-level features of the IDL; these values are stored and +treated in the semantic model as simply opaque strings, and their +validation happens before the creation of the model. + + +.. _shapes: + +------ +Shapes +------ + +Smithy models are made up of shapes. Shapes come in three kinds: simple, +aggregate, and service. A simple shape defines atomic or primitive values +such as ``integer`` and ``string``. Aggregate shapes have members such as +a list of strings or an ``Address`` structure. Service shapes have specific +semantics, unlike the very generic simple and aggregate shapes, as they +represent either a service, a resource managed by a service, or operations +on services and resources. + +Shapes are visualized using the following diagram: + +.. text-figure:: + :caption: **Figure 1.5**: Smithy shapes + :name: figure-1.5 + + ┌─────────────┐ + members ╱│ «abstract» │ + ┌───────○─│ Shape │ + │ ╲│ │ + │ └─────────────┘ + │ △ + ┌─────────│────────────────┼────────────────────┐ + │ │ │ │ + ┌───────────────┐ │ ┌─────────────┐ ┌─────────────┐ + │ «abstract» │ │container│ «abstract» │ │ «abstract» │ + │ Simple │ └────────┼│ Aggregate │ │ Service │ + └───────────────┘ └─────────────┘ └─────────────┘ + △ △ △ + ┌──────────┐ │ ┌──────────┐ │ ┌────────────┐ │ ┌─────────────────────────┐ + │blob │──┼──│boolean │ ├────│ List │ │ │ Service │ + └──────────┘ │ └──────────┘ │ ├────────────┤ │ ├─────────────────────────┤ + ┌──────────┐ │ ┌──────────┐ │ │member │ │ │version │ + │document │──┼──│string │ │ └────────────┘ ├────│operations: [Operation]? │ + └──────────┘ │ └──────────┘ │ ┌────────────┐ │ │resources: [Resource]? │ + ┌──────────┐ │ ├────│ Set │ │ └─────────────────────────┘ + │timestamp │──┤ │ ├────────────┤ │ ┌─────────────────────────┐ + └──────────┘ │ │ │member │ │ │ Operation │ + │ │ └────────────┘ │ ├─────────────────────────┤ + ┌───────────────┐ │ ┌────────────┐ │ │input: Structure? │ + │ «abstract» │ ├────│ Map │ ├────│output: Structure? │ + │ Number │ │ ├────────────┤ │ │errors: [Structure]? │ + └───────────────┘ │ │key │ │ └─────────────────────────┘ + △ │ │value │ │ ┌─────────────────────────┐ + ┌──────────┐ │ ┌──────────┐ │ └────────────┘ │ │ Resource │ + │byte │──┼──│short │ │ ┌────────────┐ │ ├─────────────────────────┤ + └──────────┘ │ └──────────┘ ├────│ Structure │ │ │identifiers? │ + ┌──────────┐ │ ┌──────────┐ │ └────────────┘ │ │create: Operation? │ + │integer │──┼──│long │ │ ┌────────────┐ │ │put: Operation? │ + └──────────┘ │ └──────────┘ └────│ Union │ │ │read: Operation? │ + ┌──────────┐ │ ┌──────────┐ └────────────┘ └────│update: Operation? │ + │float │──┼──│double │ │delete: Operation? │ + └──────────┘ │ └──────────┘ │list: : Operation? │ + ┌──────────┐ │ ┌──────────┐ │operations: [Operation]? │ + │bigInteger│──┴──│bigDecimal│ │collectionOperations: │ + └──────────┘ └──────────┘ │ [Operation]? │ + │resources: [Resource]? │ + └─────────────────────────┘ + + +.. _shape-id: + +Shape ID +======== + +All shapes have an assigned shape ID. A :dfn:`shape ID` is used to refer to +shapes in the model. Shape IDs adhere to the following syntax: + +.. code-block:: none + + com.foo.baz#ShapeName$memberName + \_________/ \_______/ \________/ + | | | + Namespace Shape name Member name + +Namespace + A namespace is a mechanism for logically grouping shapes in a way + that makes them reusable alongside other models without naming + conflicts. A semantic model MAY contain shapes defined across multiple + namespaces. The IDL representation supports zero or one namespace per + model file, while the JSON AST representation supports zero or more + namespaces per model file. +Absolute shape ID + An :dfn:`absolute shape ID` starts with a :token:`namespace` name, + followed by "``#``", followed by a *relative shape ID*. All shape + IDs in the semantic model MUST be absolute. + For example, ``smithy.example#Foo`` and ``smithy.example#Foo$bar`` + are absolute shape IDs. +Relative shape ID + A :dfn:`relative shape ID` contains a :token:`shape name ` + and an optional :token:`member name `. The shape name and + member name are separated by the "``$``" symbol if a member name is + present. For example, ``Foo`` and ``Foo$bar`` are relative shape IDs. +Root shape ID + A :dfn:`root shape ID` is a shape ID that does not contain a member. + For example, ``smithy.example#Foo`` and ``Foo`` are root shape IDs. + +.. rubric:: Shape ID ABNF + +Shape IDs are formally defined by the following ABNF: + +.. productionlist:: smithy + shape_id :`root_shape_id` [`shape_id_member`] + root_shape_id :`absolute_root_shape_id` / `identifier` + absolute_root_shape_id :`namespace` "#" `identifier` + namespace :`identifier` *("." `identifier`) + identifier :(ALPHA / "_") *(ALPHA / DIGIT / "_") + shape_id_member :"$" `identifier` + +.. rubric:: Best practices for defining shape names + +Consumers of a Smithy model MAY choose to inflect shape names, structure +member names, and other facets of a Smithy model in order to expose a more +idiomatic experience to particular programming languages. In order to make this +easier for consumers of a model, model authors SHOULD utilize a strict form of +PascalCase in which only the first letter of acronyms, abbreviations, and +initialisms are capitalized. + +=========== =============== +Recommended Not recommended +=========== =============== +UserId UserID +ResourceArn ResourceARN +IoChannel IOChannel +HtmlEntity HTMLEntity +HtmlEntity HTML_Entity +=========== =============== + + +.. _shape-id-conflicts: + +Shape ID conflicts +================== + +While shape ID references within the semantic model are case-sensitive, no +two shapes in the semantic model can have the same case-insensitive shape ID. +This restriction makes it easier to use Smithy models for code generation in +programming languages that do not support case-sensitive identifiers or that +perform some kind of normalization on generated identifiers (for example, +a Python code generator might convert all member names to lower snake case). +To illustrate, ``com.Foo#baz`` and ``com.foo#BAZ`` are not allowed in the +same semantic model. This restriction also extends to member names: +``com.foo#Baz$bar`` and ``com.foo#Baz$BAR`` are in conflict. + + +.. _simple-types: + +------------- +Simple shapes +------------- + +*Simple types* are types that do not contain nested types or shape references. + +.. list-table:: + :header-rows: 1 + :widths: 10 90 + + * - Type + - Description + * - blob + - Uninterpreted binary data + * - boolean + - Boolean value type + * - string + - UTF-8 encoded string + * - byte + - 8-bit signed integer ranging from -128 to 127 (inclusive) + * - short + - 16-bit signed integer ranging from -32,768 to 32,767 (inclusive) + * - integer + - 32-bit signed integer ranging from -2^31 to (2^31)-1 (inclusive) + * - long + - 64-bit signed integer ranging from -2^63 to (2^63)-1 (inclusive) + * - float + - Single precision IEEE-754 floating point number + * - double + - Double precision IEEE-754 floating point number + * - bigInteger + - Arbitrarily large signed integer + * - bigDecimal + - Arbitrary precision signed decimal number + * - timestamp + - Represents an instant in time with no UTC offset or timezone. The + serialization of a timestamp is an implementation detail that is + determined by a :ref:`protocol ` and + MUST NOT have any effect on the types exposed by tooling to + represent a timestamp value. + * - document + - Represents protocol-agnostic open content that is accessed like JSON + data. Open content is useful for modeling unstructured data that + has no schema, data that can't be modeled using rigid types, or data + that has a schema that evolves outside of the purview of a model. + The serialization format of a document is an implementation detail of + a protocol and MUST NOT have any effect on the types exposed by + tooling to represent a document value. + +Simple shapes are defined in the IDL using a :ref:`simple_shape_statement `. + +.. note:: + + The :ref:`prelude model ` contains pre-defined shapes for every + simple type. + +.. rubric:: Simple shape examples + +The following example defines a shape for each simple type in the +``smithy.example`` namespace. + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + blob Blob + boolean Boolean + string String + byte Byte + short Short + integer Integer + long Long + float Float + double Double + bigInteger BigInteger + bigDecimal BigDecimal + timestamp Timestamp + document Document + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#Blob": { + "type": "blob" + }, + "smithy.example#Boolean": { + "type": "boolean" + }, + "smithy.example#String": { + "type": "string" + }, + "smithy.example#Byte": { + "type": "byte" + }, + "smithy.example#Short": { + "type": "short" + }, + "smithy.example#Integer": { + "type": "integer" + }, + "smithy.example#Long": { + "type": "long" + }, + "smithy.example#Float": { + "type": "float" + }, + "smithy.example#Double": { + "type": "double" + }, + "smithy.example#BigInteger": { + "type": "bigInteger" + }, + "smithy.example#BigDecimal": { + "type": "bigDecimal" + }, + "smithy.example#Timestamp": { + "type": "timestamp" + }, + "smithy.example#Document": { + "type": "document" + } + } + } + +.. note:: + + When defining shapes in the IDL, a namespace MUST first be declared. + + +.. _aggregate-types: + +---------------- +Aggregate shapes +---------------- + +Aggregate types define shapes that are composed of other shapes. Aggregate shapes +reference other shapes using :ref:`members `. + +.. list-table:: + :header-rows: 1 + :widths: 10 90 + + * - Type + - Description + * - :ref:`member` + - Defined in aggregate shapes to reference other shapes + * - :ref:`list` + - Ordered collection of homogeneous values + * - :ref:`set` + - Unordered collection of unique homogeneous values + * - :ref:`map` + - Map data structure that maps string keys to homogeneous values + * - :ref:`structure` + - Fixed set of named heterogeneous members + * - :ref:`union` + - Tagged union data structure that can take on one of several + different, but fixed, types + + +.. _member: + +Member +====== + +:dfn:`Members` are defined in aggregate shapes to reference other shapes using +a :ref:`shape ID `. The shape referenced by a member is called its +"target". A member MUST NOT target a :ref:`trait `, ``operation``, +``resource``, ``service``, or ``member``. + + +.. _list: + +List +==== + +The :dfn:`list` type represents an ordered homogeneous collection of values. +A list shape requires a single member named ``member``. Lists are defined +in the IDL using a :ref:`list_statement `. + +The following example defines a list with a string member from the +:ref:`prelude `: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + list MyList { + member: String + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyList": { + "type": "list", + "member": { + "target": "smithy.api#String" + } + } + } + } + +.. rubric:: List member shape ID + +The shape ID of the member of a list is the list shape ID followed by +``$member``. For example, the shape ID of the list member in the above +example is ``smithy.example#MyList$member``. + + +.. _set: + +Set +=== + +The :dfn:`set` type represents an unordered collection of unique homogeneous +values. A set shape requires a single member named ``member``. +Sets are defined in the IDL using a :ref:`set_statement `. + +The following example defines a set of strings: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + set StringSet { + member: String + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#StringSet": { + "type": "set", + "member": { + "target": "smithy.api#String" + } + } + } + } + +.. rubric:: Set member shape ID + +The shape ID of the member of a set is the set shape ID followed by +``$member``. For example, the shape ID of the set member in the above +example is ``smithy.example#StringSet$member``. + +.. rubric:: Language support for sets + +Not all programming languages support set data structures. Such languages +SHOULD represent sets as a custom set data structure that can interpret value +hash codes and equality, or alternatively, store the values of a set data +structure in a list and rely on validation to ensure uniqueness. + + +.. _map: + +Map +=== + +The :dfn:`map` type represents a map data structure that maps ``string`` +keys to homogeneous values. A map requires a member named ``key`` +that MUST target a ``string`` shape and a member named ``value``. +Maps are defined in the IDL using a :ref:`map_statement `. + +The following example defines a map of strings to integers: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + map IntegerMap { + key: String, + value: Integer + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#IntegerMap": { + "type": "map", + "key": { + "target": "smithy.api#String" + }, + "value": { + "target": "smithy.api#String" + } + } + } + } + +.. rubric:: Map member shape IDs + +The shape ID of the ``key`` member of a map is the map shape ID followed by +``$key``, and the shape ID of the ``value`` member is the map shape ID +followed by ``$value``. For example, the shape ID of the ``key`` member in +the above map is ``smithy.example#IntegerMap$key``, and the ``value`` +member is ``smithy.example#IntegerMap$value``. + + +.. _structure: + +Structure +========= + +The :dfn:`structure` type represents a fixed set of named, unordered, +heterogeneous values. A structure shape contains a set of named members, and +each member name maps to exactly one :ref:`member ` definition. +Structures are defined in the IDL using a +:ref:`structure_statement `. + +The following example defines a structure with two members, one of which +is marked with the :ref:`required-trait`. + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + structure MyStructure { + foo: String, + + @required + baz: Integer, + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyStructure": { + "type": "structure", + "members": { + "foo": { + "target": "smithy.api#String" + }, + "baz": { + "target": "smithy.api#Integer", + "traits": { + "smithy.api#required": {} + } + } + } + } + } + } + +.. seealso:: + + Refer to :ref:`idl-applying-traits` for a description of how traits + are applied to shapes. + +.. rubric:: Adding new members + +New members added to existing structures SHOULD be added to the end of the +structure. This ensures that programming languages that require a specific +data structure layout or alignment for code generated from Smithy models are +able to maintain backward compatibility. + +.. rubric:: Structure member shape IDs + +The shape ID of a member of a structure is the structure shape ID, followed +by "#", followed by the member name, For example, the shape ID of the "foo" +member in the above example is ``smithy.example#MyStructure$foo``. + + +.. _union: + +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 `. + +The following example defines a union shape with several members: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + union MyUnion { + i32: Integer, + + stringA: String, + + @sensitive + stringB: String, + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyUnion": { + "type": "union", + "members": { + "i32": { + "target": "smithy.api#Integer" + }, + "stringA": { + "target": "smithy.api#String" + }, + "stringB": { + "target": "smithy.api#String", + "traits": { + "smithy.api#sensitive": {} + } + } + } + } + } + } + +.. rubric:: Adding new members + +New members added to existing unions SHOULD be added to the end of the +union. This ensures that programming languages that require a specific +data structure layout or alignment for code generated from Smithy models are +able to maintain backward compatibility. + +.. rubric:: Union member shape IDs + +The shape ID of a member of a union is the union shape ID, followed +by "#", followed by the member name. For example, the shape ID of the "i32" +member in the above example is ``smithy.example#MyUnion$i32``. + + +.. _default-values: + +Default values +============== + +The values provided for :ref:`members ` are either always present +and set to a default value when necessary or *boxed*, meaning a value is +optionally present with no default value. Members are considered boxed if +the member is marked with the :ref:`box-trait` or the shape targeted by the +member is marked with the box trait. Members that target strings, timestamps, +and aggregate shapes are always considered boxed and have no default values. + +- The default value of a ``byte``, ``short``, ``integer``, ``long``, + ``float``, and ``double`` shape that is not boxed is zero. +- The default value of a ``boolean`` shape that is not boxed is ``false``. +- All other shapes are always considered boxed and have no default value. + + +Recursive shape definitions +=========================== + +Smithy allows for recursive shape definitions with the following constraint: +the member of a list, set, or map cannot directly or transitively target its +containing shape unless one or more members in the path from the container +back to itself targets a structure or union shape. This ensures that shapes +that are typically impossible to define in various programming languages are +not defined in Smithy models (for example, you can't define a recursive list +in Java ``List` + - Entry point of an API that aggregates resources and operations together + * - :ref:`operation ` + - Represents the input, output, and errors of an API operation + * - :ref:`resource ` + - Entity with an identity that has a set of operations + +.. _service: + +Service +======= + +A :dfn:`service` is the entry point of an API that aggregates resources and +operations together. The :ref:`resources ` and +:ref:`operations ` of an API are bound within the closure of a +service. A service is defined in the IDL using a +:ref:`service_statement `. + +The service shape supports the following properties: + +.. list-table:: + :header-rows: 1 + :widths: 10 10 80 + + * - Property + - Type + - Description + * - version + - ``string`` + - **Required**. Defines the version of the service. The version can be + provided in any format (e.g., ``2017-02-11``, ``2.0``, etc). + * - :ref:`operations ` + - [``string``] + - Binds a set of ``operation`` shapes to the service. Each + element in the given list MUST be a valid :ref:`shape ID ` + that targets an :ref:`operation ` shape. + * - :ref:`resources ` + - [``string``] + - Binds a set of ``resource`` shapes to the service. Each element in + the given list MUST be a valid :ref:`shape ID ` that targets + a :ref:`resource ` shape. + +The following example defines a service with no operations or resources. + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + service MyService { + version: "2017-02-11" + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyService": { + "type": "service", + "version": "2017-02-11" + } + } + } + + +.. _service-operations: + +Service operations +------------------ + +:ref:`Operation ` shapes can be bound to a service by adding the +shape ID of an operation to the ``operations`` property of a service. +Operations bound directly to a service are typically RPC-style operations +that do not fit within a resource hierarchy. + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + service MyService { + version: "2017-02-11", + operations: [GetServerTime], + } + + @readonly + operation GetServerTime { + output: GetServerTimeOutput + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyService": { + "type": "service", + "version": "2017-02-11", + "operations": [ + { + "target": "smithy.example#GetServerTime" + } + ] + }, + "smithy.example#GetServerTime": { + "type": "operation", + "output": { + "target": "smithy.example#GetServerTimeOutput" + } + } + } + } + + +.. _service-resources: + +Service resources +----------------- + +:ref:`Resource ` shapes can be bound to a service by adding the +shape ID of a resource to the ``resources`` property of a service. + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + service MyService { + version: "2017-02-11", + resources: [MyResource], + } + + resource MyResource {} + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyService": { + "type": "service", + "version": "2017-02-11", + "resources": [ + { + "target": "smithy.example#MyResource" + } + ] + }, + "smithy.example#MyResource": { + "type": "resource" + } + } + } + + +.. _service-closure: + +Service closure +--------------- + +The *closure* of a service is the set of shapes connected to a service +through resources, operations, and members. + +.. important:: + + With some exceptions, the shapes that are referenced in the *closure* + of a service MUST have case-insensitively unique names regardless of + their namespace. + +By requiring unique names within a service, each service forms a +`ubiquitous language`_, making it easier for developers to understand the +model and artifacts generated from the model, like code. For example, when +using Java code generated from a Smithy model, a developer should not need +to discern between ``BadRequestException`` classes across multiple packages +that can be thrown by an operation. Uniqueness is required +case-insensitively because many model transformations (like code generation) +change the casing and inflection of shape names to make artifacts more +idiomatic. + +:ref:`Simple types ` and :ref:`lists ` or +:ref:`sets ` of compatible simple types are allowed to conflict because +a conflict for these type would rarely have an impact on generated artifacts. +These kinds of conflicts are only allowed if both conflicting shapes are the +same type and have the exact same traits. In the case of a list or set, a +conflict is only allowed if both types target compatible shapes. + +An operation or resource MUST NOT be bound to multiple shapes within the +closure of a service. This constraint allows services to discern between +operations and resources using only their shape name rather than a +fully-qualified path from the service to the shape. + + +.. _operation: + +Operation +========= + +The :dfn:`operation` type represents the input, output, and possible errors of +an API operation. Operation shapes are bound to :ref:`resource ` +shapes and :ref:`service ` shapes. An operation is defined in the IDL +using an :ref:`operation_statement `. + +An operation supports the following members: + +.. list-table:: + :header-rows: 1 + :widths: 10 10 80 + + * - Property + - Type + - 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`. + * - 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`. + * - 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 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 `. + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + operation MyOperation { + input: Input, + output: Output, + errors: [NotFound, BadRequest] + } + + .. code-tab:: json + + { + "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" + } + ] + } + } + } + + +.. _resource: + +Resource +======== + +Smithy defines a :dfn:`resource` as an entity with an identity that has a +set of operations. A resource shape is defined in the IDL using a +:ref:`resource_statement `. + +A resource supports the following members: + +.. list-table:: + :header-rows: 1 + :widths: 10 10 80 + + * - Property + - Type + - Description + * - :ref:`identifiers ` + - ``object`` + - Defines a map of identifier string names to :ref:`shape-id`\s used to + identify the resource. Each shape ID MUST target a ``string`` shape. + * - :ref:`create ` + - ``string`` + - Defines the lifecycle operation used to create a resource using one + or more identifiers created by the service. The value MUST be a + valid :ref:`shape-id` that targets an ``operation`` shape. + * - :ref:`put ` + - ``string`` + - Defines an idempotent lifecycle operation used to create a resource + using identifiers provided by the client. The value MUST be a + valid :ref:`shape-id` that targets an ``operation`` shape. + * - :ref:`read ` + - ``string`` + - Defines the lifecycle operation used to retrieve the resource. The + value MUST be a valid :ref:`shape-id` that targets an + ``operation`` shape. + * - :ref:`update ` + - ``string`` + - Defines the lifecycle operation used to update the resource. The + value MUST be a valid :ref:`shape-id` that targets an + ``operation`` shape. + * - :ref:`delete ` + - ``string`` + - Defines the lifecycle operation used to delete the resource. The + value MUST be a valid :ref:`shape-id` that targets an ``operation`` + shape. + * - :ref:`list ` + - ``string`` + - Defines the lifecycle operation used to list resources of this type. + The value MUST be a valid :ref:`shape-id` that targets an + ``operation`` shape. + * - operations + - [``string``] + - Binds a list of non-lifecycle instance operations to the resource. + Each value in the list MUST be a valid :ref:`shape-id` that targets + an ``operation`` shape. + * - collectionOperations + - [``string``] + - Binds a list of non-lifecycle collection operations to the resource. + Each value in the list MUST be a valid :ref:`shape-id` that targets + an ``operation`` shape. + * - resources + - [``string``] + - Binds a list of resources to this resource as a child resource, + forming a containment relationship. Each value in the list MUST be a + valid :ref:`shape-id` that targets a ``resource``. The resources + MUST NOT have a cyclical containment hierarchy, and a resource + can not be bound more than once in the entire closure of a + resource or service. + + +.. _resource-identifiers: + +Resource Identifiers +==================== + +:dfn:`Identifiers` are used to refer to a specific resource within a service. +The identifiers property of a resource is a map of identifier names to +:ref:`shape IDs ` that MUST target string shapes. + +For example, the following model defines a ``Forecast`` resource with a +single identifier named ``forecastId`` that targets the ``ForecastId`` shape: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + resource Forecast { + identifiers: { + forecastId: ForecastId + } + } + + string ForecastId + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#Forecast": { + "type": "resource", + "identifiers": { + "forecastId": { + "target": "smithy.example#ForecastId" + } + } + }, + "smithy.example#ForecastId": { + "type": "string" + } + } + } + +When a resource is bound as a child to another resource using the "resources" +property, all of the identifiers of the parent resource MUST be repeated +verbatim in the child resource, and the child resource MAY introduce any +number of additional identifiers. + +:dfn:`Parent identifiers` are the identifiers of the parent of a resource. +All parent identifiers MUST be bound as identifiers in the input of every +operation bound as a child to a resource. :dfn:`Child identifiers` are the +identifiers that a child resource contains that are not present in the parent +identifiers. + +For example, given the following model, + +.. tabs:: + + .. code-tab:: smithy + + resource ResourceA { + identifiers: { + a: String + }, + resources: [ResourceB], + } + + resource ResourceB { + identifiers: { + a: String, + b: String, + }, + resources: [ResourceC], + } + + resource ResourceC { + identifiers: { + a: String, + b: String, + c: String, + } + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#ResourceA": { + "type": "resource", + "resources": [ + { + "target": "smithy.example#ResourceB" + } + ], + "identifiers": { + "a": { + "target": "smithy.api#String" + } + } + }, + "smithy.example#ResourceB": { + "type": "resource", + "resources": [ + { + "target": "smithy.example#ResourceC" + } + ], + "identifiers": { + "a": { + "target": "smithy.api#String" + }, + "b": { + "target": "smithy.api#String" + } + } + }, + "smithy.example#ResourceC": { + "type": "resource", + "identifiers": { + "a": { + "target": "smithy.api#String" + }, + "b": { + "target": "smithy.api#String" + }, + "c": { + "target": "smithy.api#String" + } + } + } + } + } + +``ResourceB`` is a valid child of ``ResourceA`` and contains a child +identifier of "b". ``ResourceC`` is a valid child of ``ResourceB`` and +contains a child identifier of "c". + +However, the following defines two *invalid* child resources that do not +define an ``identifiers`` property that is compatible with their parents: + +.. tabs:: + + .. code-tab:: smithy + + resource ResourceA { + identifiers: { + a: String, + b: String, + }, + resources: [Invalid1, Invalid2], + } + + resource Invalid1 { + // Invalid: missing "a". + identifiers: { + b: String, + }, + } + + resource Invalid2 { + identifiers: { + a: String, + // Invalid: does not target the same shape. + b: SomeOtherString, + }, + } + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#ResourceA": { + "type": "resource", + "identifiers": { + "a": { + "target": "smithy.api#String" + }, + "b": { + "target": "smithy.api#String" + } + }, + "resources": [ + { + "target": "smithy.example#Invalid1" + }, + { + "target": "smithy.example#Invalid2" + } + ] + }, + "smithy.example#Invalid1": { + "type": "resource", + "identifiers": { + "b": { + "target": "smithy.api#String" + } + } + }, + "smithy.example#Invalid2": { + "type": "resource", + "identifiers": { + "a": { + "target": "smithy.api#String" + }, + "b": { + "target": "smithy.example#SomeOtherString" + } + } + } + } + } + +.. _binding-identifiers: + +Binding identifiers to operations +--------------------------------- + +*Identifier bindings* indicate which top-level members of the input structure +of an operation provide values for the identifiers of a resource. + +.. rubric:: Identifier binding validation + +- Child resources MUST provide identifier bindings for all of its parent's + identifiers. +- Identifier bindings are only formed on input structure members that are + marked as :ref:`required `. +- Resource operations MUST form a valid *instance operation* or + *collection operation*. + +.. _instance-operations: + +:dfn:`Instance operations` are formed when all of the identifiers of a resource +are bound to the input structure of an operation or when a resource has no +identifiers. The :ref:`put `, :ref:`read `, +:ref:`update `, and :ref:`delete ` +lifecycle operations are examples of instance operations. An operation bound +to a resource using `operations` MUST form a valid instance operation. + +.. _collection-operations: + +:dfn:`Collection operations` are used when an operation is meant to operate on +a collection of resources rather than a specific resource. Collection +operations are formed when an operation is bound to a resource with `collectionOperations`, +or when bound to the :ref:`list ` or :ref:`create ` +lifecycle operations. A collection operation MUST omit one or more identifiers +of the resource it is bound to, but MUST bind all identifiers of any parent +resource. + + +.. _implicit-identifier-bindings: + +Implicit identifier bindings +---------------------------- + +*Implicit identifier bindings* are formed when the input of an operation +contains member names that target the same shapes that are defined in the +"identifiers" property of the resource to which an operation is bound. + +For example, given the following model, + +.. tabs:: + + .. code-tab:: smithy + + resource Forecast { + identifiers: { + forecastId: ForecastId, + }, + read: GetForecast, + } + + @readonly + operation GetForecast { + input: GetForecastInput, + output: GetForecastOutput + } + + structure GetForecastInput { + @required + forecastId: ForecastId, + } + + structure GetForecastOutput { + @required + weather: WeatherData, + } + + .. code-tab:: json + + { + "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": {} + } + } + } + } + } + } + +``GetForecast`` forms a valid instance operation because the operation is +not marked with the ``collection`` trait and ``GetForecastInput`` provides +*implicit identifier bindings* by defining a required "forecastId" member +that targets the same shape as the "forecastId" identifier of the resource. + +Implicit identifier bindings for collection operations are created in a +similar way to an instance operation, but MUST NOT contain identifier bindings +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 + } + + structure BatchPutForecastsInput { + @required + forecasts: BatchPutForecastList, + } + + .. code-tab:: json + + { + "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": {} + } + } + } + } + } + } + +``BatchPutForecasts`` forms a valid collection operation with implicit +identifier bindings because ``BatchPutForecastsInput`` does not require an +input member named "forecastId" that targets ``ForecastId``. + + +Explicit identifier bindings +---------------------------- + +*Explicit identifier bindings* are defined by applying the +:ref:`resourceIdentifier-trait` to a member of the input of for an +operation bound to a resource. Explicit bindings are necessary when the name of +the input structure member differs from the name of the resource identifier to +which the input member corresponds. + +For example, given the following, + +.. code-block:: smithy + + resource Forecast { + // continued from above + resources: [HistoricalForecast], + } + + resource HistoricalForecast { + identifiers: { + forecastId: ForecastId, + historicalId: HistoricalForecastId, + }, + read: GetHistoricalForecast, + list: ListHistoricalForecasts, + } + + @readonly + operation GetHistoricalForecast { + input: GetHistoricalForecastInput, + output: GetHistoricalForecastOutput + } + + structure GetHistoricalForecastInput { + @required + @resourceIdentifier("forecastId") + customForecastIdName: ForecastId, + + @required + @resourceIdentifier("historicalId") + customHistoricalIdName: String + } + +the :ref:`resourceIdentifier-trait` on ``GetHistoricalForecastInput$customForecastIdName`` +maps it to the "forecastId" identifier is provided by the +"customForecastIdName" member, and the :ref:`resourceIdentifier-trait` +on ``GetHistoricalForecastInput$customHistoricalIdName`` maps that member +to the "historicalId" identifier. + + +.. _lifecycle-operations: + +Resource lifecycle operations +============================= + +:dfn:`Lifecycle operations` are used to transition the state of a resource +using well-defined semantics. Lifecycle operations are defined by providing a +shape ID to the ``put``, ``create``, ``read``, ``update``, ``delete``, and +``list`` properties of a resource. Each shape ID MUST target an +:ref:`operation ` that is compatible with the semantics of the +lifecycle. + +The following example defines a resource with each lifecycle method: + +.. code-block:: smithy + + namespace smithy.example + + resource Forecast { + identifiers: { + forecastId: ForecastId, + }, + put: PutForecast, + create: CreateForecast, + read: GetForecast, + update: UpdateForecast, + delete: DeleteForecast, + list: ListForecasts, + } + + +.. _put-lifecycle: + +Put lifecycle +------------- + +The ``put`` lifecycle operation is used to create a resource using identifiers +provided by the client. + +- Put operations MUST NOT be marked with the :ref:`readonly-trait`. +- Put operations MUST be marked with the :ref:`idempotent-trait`. +- Put operations MUST form valid :ref:`instance operations `. + +The following example defines the ``PutForecast`` operation. + +.. code-block:: smithy + + @idempotent + operation PutForecast { + input: PutForecastInput, + output: PutForecastOutput + } + + structure PutForecastInput { + // The client provides the resource identifier. + @required + forecastId: ForecastId, + + chanceOfRain: Float + } + +.. rubric:: Put semantics + +The semantics of a ``put`` lifecycle operation are similar to the semantics +of a HTTP PUT method as described in :rfc:`section 4.3.4 of [RFC7231] <7231#section-4.3.4>`: + + The PUT method requests that the state of the target resource be + created or replaced ... + +The :ref:`noReplace-trait` can be applied to resources that define a +``put`` lifecycle operation to indicate that a resource cannot be +replaced using the ``put`` operation. + + +.. _create-lifecycle: + +Create lifecycle +---------------- + +The ``create`` operation is used to create a resource using one or more +identifiers created by the service. + +- Create operations MUST NOT be marked with the :ref:`readonly-trait`. +- Create operations MUST form valid :ref:`collection operations `. +- The ``create`` operation MAY be marked with the :ref:`idempotent-trait`. + +The following example defines the ``CreateForecast`` operation. + +.. code-block:: smithy + + operation CreateForecast { + input: CreateForecastInput, + output: CreateForecastOutput + } + + operation CreateForecast { + input: CreateForecastInput, + output: CreateForecastOutput + } + + structure CreateForecastInput { + // No identifier is provided by the client, so the service is + // responsible for providing the identifier of the resource. + chanceOfRain: Float, + } + + +.. _read-lifecycle: + +Read lifecycle +-------------- + +The ``read`` operation is the canonical operation used to retrieve the current +representation of a resource. + +- Read operations MUST be valid :ref:`instance operations `. +- Read operations MUST be marked with the :ref:`readonly-trait`. + +For example: + +.. code-block:: smithy + + @readonly + operation GetForecast { + input: GetForecastInput, + output: GetForecastOutput, + errors: [ResourceNotFound] + } + + structure GetForecastInput { + @required + forecastId: ForecastId, + } + + +.. _update-lifecycle: + +Update lifecycle +---------------- + +The ``update`` operation is the canonical operation used to update a +resource. + +- Update operations MUST be valid :ref:`instance operations `. +- Update operations MUST NOT be marked with the :ref:`readonly-trait`. + +For example: + +.. code-block:: smithy + + operation UpdateForecast { + input: UpdateForecastInput, + output: UpdateForecastOutput, + errors: [ResourceNotFound] + } + + structure UpdateForecastInput { + @required + forecastId: ForecastId, + + chanceOfRain: Float, + } + + +.. _delete-lifecycle: + +Delete lifecycle +---------------- + +The ``delete`` operation is canonical operation used to delete a resource. + +- Delete operations MUST be valid :ref:`instance operations `. +- Delete operations MUST NOT be marked with the :ref:`readonly-trait`. +- Delete operations MUST be marked with the :ref:`idempotent-trait`. + +For example: + +.. code-block:: smithy + + @idempotent + operation DeleteForecast { + input: DeleteForecastInput, + output: DeleteForecastOutput, + errors: [ResourceNotFound] + } + + structure DeleteForecastInput { + @required + forecastId: ForecastId, + } + + +.. _list-lifecycle: + +List lifecycle +-------------- + +The ``list`` operation is the canonical operation used to list a +collection of resources. + +- List operations MUST form valid :ref:`collection operations `. +- List operations MUST be marked with the :ref:`readonly-trait`. +- The output of a list operation SHOULD contain references to the resource + being listed. +- List operations SHOULD be :ref:`paginated `. + +For example: + +.. code-block:: smithy + + @readonly @paginated + operation ListForecasts { + input: ListForecastsInput, + output: ListForecastsOutput + } + + structure ListForecastsInput { + maxResults: Integer, + nextToken: String + } + + structure ListForecastsOutput { + nextToken: String, + @required + forecasts: ForecastList + } + + list ForecastList { + member: ForecastId + } + + +.. _traits: + +------ +Traits +------ + +*Traits* are model components that can be attached to :ref:`shapes ` +to describe additional information about the shape; shapes provide the +structure and layout of an API, while traits provide refinement and style. + + +.. _applying-traits: + +Applying traits to shapes +========================= + +An instance of a trait applied to a shape is called an *applied trait*. Only +a single instance of a trait can be applied to a shape. The way in which a +trait is applied to a shape depends on the model file representation. + +Traits are applied to shapes in the IDL using :token:`trait_statements` that +immediately precede a shape. The following example applies the +:ref:`sensitive-trait` and :ref:`documentation-trait` to ``MyString``: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + @sensitive + @documentation("Contains a string") + string MyString + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyString": { + "type": "string", + "traits": { + "smithy.api#documentation": "Contains a string", + "smithy.api#sensitive": {} + } + } + } + } + +* Refer to the :ref:`IDL specification ` for a + description of how traits are applied in the IDL. +* Refer to the :ref:`JSON AST specification ` for a + description of how traits are applied in the JSON AST. + +.. rubric:: Applying traits externally + +Both the IDL and JSON AST model representations allow traits to be applied +to shapes outside of a shape's definition. This is done using an +:token:`apply ` statement in the IDL, or the +:ref:`apply ` type in the JSON AST. For example, this can be +useful to allow different teams within the same organization to independently +own different facets of a model; a service team could own the model that +defines the shapes and traits of the API, and a documentation team could +own a model that applies documentation traits to the shapes. + +The following example applies the :ref:`documentation-trait` and +:ref:`length-trait` to the ``smithy.example#MyString`` shape: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + apply MyString @documentation("This is my string!") + apply MyString @length(min: 1, max: 10) + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#MyString": { + "type": "apply", + "traits": { + "smithy.api#documentation": "This is my string!", + "smithy.api#length": { + "min": 1, + "max": 10 + } + } + } + } + } + +.. note:: + + In the semantic model, applying traits outside of a shape definition is + treated exactly the same as applying the trait inside of a shape + definition. + +.. rubric:: Scope of member traits + +Traits that target :ref:`members ` apply only in the context of +the member shape and do not affect the shape targeted by the member. Traits +applied to a member supersede traits applied to the shape targeted by the +member and do not inherently conflict. + + +.. _trait-conflict-resolution: + +Trait conflict resolution +========================= + +Trait conflict resolution is used when the same trait is applied multiple +times to a shape. Duplicate traits applied to shapes are allowed in the +following cases: + +1. If the trait is a ``list`` or ``set`` shape, then the conflicting trait + values are concatenated into a single trait value. +2. If both values are exactly equal, then the conflict is ignored. + +All other instances of trait collisions are prohibited. + +The following model definition is **valid** because the ``length`` trait is +duplicated on the ``MyList`` shape with the same values: + +.. code-block:: smithy + + namespace smithy.example + + @length(min: 0, max: 10) + list MyList { + member: String + } + + apply MyList @length(min: 0, max: 10) + +The following model definition is **valid** because the ``tags`` trait +is a list. The resulting value assigned to the ``tags`` trait on the +``Hello`` shape is a list that contains "a", "b", and "c". + +.. code-block:: smithy + + namespace smithy.example + + @tags(["a", "b"]) + string Hello + + apply Hello @tags(["c"]) + +The following model definition is **invalid** because the ``length`` trait is +duplicated on the ``MyList`` shape with different values: + +.. code-block:: smithy + + namespace smithy.example + + @length(min: 0, max: 10) + list MyList { + member: String + } + + apply MyList @length(min: 10, max: 20) + + +.. _trait-node-values: + +Trait node values +================= + +The value provided for a trait MUST be compatible with the ``shape`` of the +trait. The following table defines each shape type that is available to +target from traits and how their values are defined in +:token:`node ` values. + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - Smithy type + - Node type + - Description + * - blob + - string + - A ``string`` value that is base64 encoded. + * - boolean + - boolean + - Can be set to ``true`` or ``false``. + * - byte + - number + - The value MUST fall within the range of -128 to 127 + * - short + - number + - The value MUST fall within the range of -32,768 to 32,767 + * - integer + - number + - The value MUST fall within the range of -2^31 to (2^31)-1. + * - long + - number + - The value MUST fall within the range of -2^63 to (2^63)-1. + * - float + - number + - A normal JSON number. + * - double + - number + - A normal JSON number. + * - bigDecimal + - string | number + - bigDecimal values can be serialized as strings to avoid rounding + issues when parsing a Smithy model in various languages. + * - bigInteger + - string | number + - bigInteger values can be serialized as strings to avoid truncation + issues when parsing a Smithy model in various languages. + * - string + - string + - The provided value SHOULD be compatible with the ``mediaType`` of the + string shape if present; however, this is not validated by Smithy. + * - timestamp + - number | string + - If a number is provided, it represents Unix epoch seconds with optional + millisecond precision. If a string is provided, it MUST be a valid + :rfc:`3339` string with optional millisecond precision and no + UTC offset (for example, ``1990-12-31T23:59:60Z``). + * - list and set + - array + - Each value in the array MUST be compatible with the targeted member. + * - map + - object + - Each key MUST be compatible with the ``key`` member of the map, and + each value MUST be compatible with the ``value`` member of the map. + * - structure + - object + - All members marked as required MUST be provided in a corresponding + key-value pair. Each key MUST correspond to a single member name of + the structure. Each value MUST be compatible with the member that + corresponds to the member name. + * - union + - object + - The object MUST contain a single single key-value pair. The key MUST be + one of the member names of the union shape, and the value MUST be + compatible with the corresponding shape. + +.. rubric:: Constraint traits + +Trait values MUST be compatible with any constraint traits found related to the +shape being validated. + + +.. _trait-shapes: + +.. _defining-traits: + +Defining traits +=============== + +Traits are defined inside of a namespace by applying ``smithy.api#trait`` to +a shape. This trait can only be applied to simple types, ``list``, ``map``, +``set``, ``structure``, and ``union`` shapes. + +The following example defines a trait with a :ref:`shape ID ` of +``smithy.example#myTraitName`` and applies it to ``smithy.example#MyString``: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + @trait(selector: "*") + structure myTraitName {} + + @myTraitName + string MyString + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#myTraitName": { + "type": "structure", + "traits": { + "smithy.api#trait": { + "selector": "*" + } + } + }, + "smithy.example#MyString": { + "type": "string", + "traits": { + "smithy.api#myTraitName": {} + } + } + } + } + +.. rubric:: Trait properties + +``smithy.api#trait`` is a structure that supports the following members: + +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - selector + - ``string`` + - A valid :ref:`selector ` that defines where the trait + can be applied. For example, a ``selector`` set to ``:test(list, map)`` + means that the trait can be applied to a :ref:`list ` or + :ref:`map ` shape. This value defaults to ``*`` if not set, + meaning the trait can be applied to any shape. + * - conflicts + - [``string``] + - Defines the shape IDs of traits that MUST NOT be applied to the same + shape as the trait being defined. This allows traits to be defined as + mutually exclusive. Provided shape IDs MAY target unknown traits + that are not defined in the model. + * - structurallyExclusive + - ``string`` + - One of "member" or "target". When set to "member", only a single + member of a structure can be marked with the trait. When set to + "target", only a single member of a structure can target a shape + marked with this trait. + +The following example defines two custom traits: ``beta`` and +``structuredTrait``: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + /// A trait that can be applied to a member. + @trait(selector: "structure > member") + structure beta {} + + /// A trait that has members. + @trait(selector: "string", conflicts: [beta]) + structure structuredTrait { + @required + lorem: StringShape, + + @required + ipsum: StringShape, + + dolor: StringShape, + } + + // Apply the "beta" trait to the "foo" member. + structure MyShape { + @required + @beta + foo: StringShape, + } + + // Apply the structuredTrait to the string. + @structuredTrait( + lorem: "This is a custom trait!", + ipsum: "lorem and ipsum are both required values.") + string StringShape + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#beta": { + "type": "apply", + "traits": { + "smithy.api#type": "structure", + "smithy.api#trait": { + "selector": "structure > member" + }, + "smithy.api#documentation": "A trait that can be applied to a member." + } + }, + "smithy.example#structuredTrait": { + "type": "apply", + "traits": { + "smithy.api#type": "structure", + "smithy.api#trait": { + "selector": "string", + "conflicts": [ + "smithy.example#beta" + ] + }, + "smithy.api#members": { + "lorem": { + "target": "StringShape", + "required": true + }, + "dolor": { + "target": "StringShape" + } + }, + "smithy.api#documentation": "A trait that has members." + } + }, + "smithy.example#MyShape": { + "type": "apply", + "traits": { + "smithy.api#type": "structure", + "smithy.api#members": { + "beta": { + "target": "StringShape", + "required": true, + "beta": true + } + } + } + }, + "smithy.example#StringShape": { + "type": "apply", + "traits": { + "smithy.api#type": "string", + "smithy.api#structuredTrait": { + "lorem": "This is a custom trait!", + "ipsum": "lorem and ipsum are both required values." + } + } + } + } + } + +.. rubric:: Prelude traits + +When using the IDL, built-in traits defined in the Smithy +:ref:`prelude ` namespace, ``smithy.api``, are automatically +available in every Smithy model and namespace through relative shape IDs. + +.. rubric:: References to traits + +The only valid reference to a trait is through applying a trait to a +shape. Members and references within a model MUST NOT target shapes. + +.. rubric:: Naming traits + +By convention, trait shape names SHOULD use a lowercase name so that they +visually stand out from normal shapes. + + +.. _annotation-trait: + +Annotation traits +----------------- + +A structure trait with no members is called an *annotation trait*. It's hard +to predict what information a trait needs to capture when modeling a domain; +a trait might start out as a simple annotation, but later might benefit +from additional information. By defining an annotation trait rather than a +boolean trait, the trait can safely add optional members over time as needed. + +The following example defines an annotation trait named ``foo``: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + @trait + structure foo {} + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#foo": { + "type": "structure", + "traits": { + "smithy.api#trait": {} + } + } + } + } + +A member can be safely added to an annotation trait if the member is not +marked as :ref:`required `. The applications of the ``foo`` +trait in the previous example and the following example are all valid even +after adding a member to the ``foo`` trait: + +.. tabs:: + + .. code-tab:: smithy + + namespace smithy.example + + @trait + structure foo { + baz: String, + } + + @foo(baz: "bar") + string MyString4 + + .. code-tab:: json + + { + "smithy": "1.0", + "shapes": { + "smithy.example#foo": { + "type": "structure", + "members": { + "baz": { + "target": "smithy.api#String" + } + }, + "traits": { + "smithy.api#trait": {} + } + }, + "smithy.example#MyString4": { + "type": "string", + "traits": { + "smithy.api#foo": { + "baz": "bar" + } + } + } + } + } + + +.. _tagged union data structure: https://en.wikipedia.org/wiki/Tagged_union +.. _ubiquitous language: https://martinfowler.com/bliki/UbiquitousLanguage.html diff --git a/docs/source/1.0/spec/core/prelude-model.rst b/docs/source/1.0/spec/core/prelude-model.rst index 918398bc190..fa3cac1942a 100644 --- a/docs/source/1.0/spec/core/prelude-model.rst +++ b/docs/source/1.0/spec/core/prelude-model.rst @@ -6,9 +6,8 @@ Prelude model All Smithy models automatically include a *prelude*. The prelude defines various simple shapes and every trait defined in the core specification. -Shapes defined in the prelude can be referenced from within any namespace -using a relative shape ID. - +When using the :ref:`IDL `, shapes defined in the prelude can be +referenced from within any namespace using a relative shape ID. .. tip:: diff --git a/docs/source/1.0/spec/core/protocol-traits.rst b/docs/source/1.0/spec/core/protocol-traits.rst index 8d5c9eb7dc3..ac14d8231e3 100644 --- a/docs/source/1.0/spec/core/protocol-traits.rst +++ b/docs/source/1.0/spec/core/protocol-traits.rst @@ -44,9 +44,8 @@ Value type * - noInlineDocumentSupport - ``boolean`` - If set to ``true``, indicates that this protocol does not support - :ref:`document ` shapes. A service that uses such - a protocol MUST NOT contain any document shapes in their service - closure. + ``document`` type shapes. A service that uses such a protocol + MUST NOT contain any ``document`` shapes in their service closure. Smithy is protocol agnostic, which means it focuses on the interfaces and abstractions that are provided to end-users rather than how the data is sent @@ -248,11 +247,12 @@ Trait selector Value type ``string`` -The serialization format of a timestamp shape is normally dictated by the -:ref:`protocol ` of a service. In order to -interoperate with other web services or frameworks, it is sometimes -necessary to use a specific serialization format that differs from the -protocol. +By default, the serialization format of a timestamp is implicitly determined by +the :ref:`protocol ` of a service; however, the +serialization format can be explicitly configured in some protocols to +override the default format using the ``timestampFormat`` trait. + +.. rubric:: Timestamp formats Smithy defines the following built-in timestamp formats: @@ -275,13 +275,17 @@ Smithy defines the following built-in timestamp formats: 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970, with decimal precision (for example, ``1515531081.1234``). +.. rubric:: Resolving timestamp formats + +The following steps are taken to determine the serialization format of a +:ref:`member ` that targets a timestamp: + +1. Use the ``timestampFormat`` trait of the member, if present. +2. Use the ``timestampFormat`` trait of the shape, if present. +3. Use the default format of the protocol. + .. important:: This trait SHOULD NOT be used unless the intended serialization format of a timestamp differs from the default protocol format. Using this trait too liberally can cause other tooling to improperly interpret the timestamp. - -.. seealso:: - - Refer to :ref:`timestamp-serialization-format` for information on how to - determine the serialization format of a timestamp. diff --git a/docs/source/1.0/spec/core/resource-traits.rst b/docs/source/1.0/spec/core/resource-traits.rst index 63539a82adf..eb63f5119b1 100644 --- a/docs/source/1.0/spec/core/resource-traits.rst +++ b/docs/source/1.0/spec/core/resource-traits.rst @@ -83,15 +83,9 @@ to call ``CreateTable`` on a table that already exists will return an error. -------------------- Summary - Defines the :ref:`resource` shapes that are referenced by a string shape - or a structure shape and the members of the structure that provide values - for the :ref:`identifiers ` of the resource. - - References provide the ability for tooling to *dereference* a resource - reference at runtime. For example, if a client receives a response from a - service that contains references, the client could provide functionality - to resolve references by name, allowing the end-user to invoke operations - on a specific referenced resource. + Defines a design-time reference to :ref:`resource` shapes. Resource + references allow tooling to understand the relationships between + resources and how to dereference the location of a resource. Trait selector ``:is(structure, string)`` @@ -99,6 +93,8 @@ Trait selector Value type ``list`` of ``Reference`` structures +.. rubric:: ``Reference`` structure + The ``references`` trait is a list of ``Reference`` structures that contain the following members: @@ -141,6 +137,8 @@ the following members: contain a link relation as defined in :rfc:`5988#section-4` (i.e., this value SHOULD contain either a `standard link relation`_ or URI). +.. rubric:: Runtime resolution of references + References MAY NOT be resolvable at runtime in the following circumstances: #. The members that make up the ``ids`` are not present in a structure at @@ -148,6 +146,58 @@ References MAY NOT be resolvable at runtime in the following circumstances: #. The targeted resource and/or service shape is not part of the model #. The reference is bound to a specific service that is unknown to the tool +.. rubric:: Implicit identifier mappings example + +The following example creates a reference to a ``HistoricalForecast`` resource +(a resource that requires the "forecastId" and "historicalId" identifiers): + +.. code-block:: smithy + + namespace smithy.example + + resource HistoricalForecast { + identifiers: { + forecastId: ForecastId, + historicalId: HistoricalForecastId, + } + } + + @references([{resource: HistoricalForecast}]) + structure HistoricalReference { + forecastId: ForecastId, + historicalId: HistoricalForecastId + } + +Notice that in the above example, the identifiers of the resource were not +explicitly mapped to structure members. This is because the targeted structure +contains members with names that match the names of the identifiers of the +``HistoricalForecast`` resource. + +.. rubric:: Explicit identifier mappings example + +Explicit mappings between identifier names and structure member names can be +defined if needed. For example: + +.. code-block:: smithy + + namespace smithy.example + + @references([ + { + resource: HistoricalForecast, + ids: { + forecastId: "customForecastId", + historicalId: "customHistoricalId" + } + } + ]) + structure AnotherHistoricalReference { + customForecastId: String, + customHistoricalId: String, + } + +.. rubric:: Additional examples + The following example defines several references: .. tabs:: @@ -182,6 +232,23 @@ The following example defines several references: objectKey: ObjectKey, } +.. rubric:: References on string shapes + +A reference can be formed on a string shape for resources that have one +identifier. References applied to a string shape MUST omit the "ids" +property in the reference. + +.. code-block:: smithy + + resource SimpleResource { + identifiers: { + foo: String, + } + } + + @references([{resource: SimpleResource}]) + string SimpleResourceReference + .. _implicit-ids: diff --git a/docs/source/1.0/spec/core/selectors.rst b/docs/source/1.0/spec/core/selectors.rst index 56e37073eb6..3da01959151 100644 --- a/docs/source/1.0/spec/core/selectors.rst +++ b/docs/source/1.0/spec/core/selectors.rst @@ -7,7 +7,7 @@ Selectors A :dfn:`Smithy selector` is a domain specific language (DSL) used to match shapes within a model. Selectors are used to build custom :ref:`validators ` and to specify where it is valid to -apply a :ref:`trait `. +apply a :ref:`trait `. .. contents:: Table of contents :depth: 1 diff --git a/docs/source/1.0/spec/core/shapes.rst b/docs/source/1.0/spec/core/shapes.rst deleted file mode 100644 index de7524d83fe..00000000000 --- a/docs/source/1.0/spec/core/shapes.rst +++ /dev/null @@ -1,2186 +0,0 @@ -.. _shapes: - -====== -Shapes -====== - -*Shapes* are named data definitions that describe the structure of an API. -Shapes have a *type* that represents the type of values that a shape -represents. Types like ``string``, ``structure``, and ``operation`` represent -the kinds of shapes that can be used in a model, while shapes like -``MyString``, ``MyStructure``, and ``MyOperation`` define a referenceable -instance of a type. - -.. contents:: Table of contents - :depth: 1 - :local: - :backlinks: none - - -------------- -Shape section -------------- - -The shape section of the IDL is used to define shapes and apply traits to -shapes. It comes after any :token:`control statements ` and -:token:`metadata statements `. - -.. productionlist:: smithy - shape_section :[`namespace_statement` [`use_section`] [`shape_statements`]] - - -.. _namespaces: - -Namespaces -========== - -Shapes are defined inside a :dfn:`namespace`. A namespace is mechanism for -logically grouping shapes in a way that makes them reusable alongside other -models without naming conflicts. - -.. _namespace-statement: - -Shapes can only be defined if a namespace is declared, and only a single -namespace can appear in an IDL model file. - -.. productionlist:: smithy - namespace_statement :"namespace" `ws` `namespace` `br` - -The following example defines a string shape named ``MyString`` in the -``smithy.example`` namespace: - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - string MyString - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyString": { - "type": "string" - } - } - } - - -.. _use-statement: - -Referring to shapes -=================== - -The *use section* of the IDL is used to import shapes into the current -namespace so that they can be referred to using a relative shape. - -.. productionlist:: smithy - use_section :*(`use_statement`) - use_statement :"use" `ws` `absolute_root_shape_id` `br` - -The following example imports ``smithy.example#Foo`` and -``smithy.example#Baz`` so that they can be referred to by -:ref:`relative shape IDs `: - -.. code-block:: smithy - - namespace smithy.hello - - use smithy.example#Foo - use smithy.example#Baz - - map MyMap { - // Resolves to smithy.example#Foo - key: Foo, - // Resolves to smithy.example#Baz - value: Baz, - } - -A use statement can import :ref:`traits ` too. The following example -imports the ``smithy.example#test`` and ``smithy.example#anotherTrait`` -traits so that they can be applied using relative shape IDs: - -.. code-block:: smithy - - namespace smithy.hello - - use smithy.example#test - use smithy.example#anotherTrait - - @test // <-- Resolves to smithy.example#test - string MyString - -.. rubric:: Validation - -#. A shape cannot be defined in a file with the same name as one of the - shapes imported with a ``use`` statement. -#. Shapes IDs with members names cannot be imported with a use statement. - - -.. _relative-shape-id: - -Relative shape ID resolution ----------------------------- - -In the Smithy IDL, relative shape IDs are resolved using the following process: - -#. If a :token:`use_statement` has imported a shape with the same name, - the shape ID resolves to the imported shape ID. -#. If a shape is defined in the same namespace as the shape with the same name, - the namespace of the shape resolves to the *current namespace*. -#. If a shape is defined in the :ref:`prelude ` with the same name, - the namespace resolves to ``smithy.api``. -#. If a relative shape ID does not satisfy one of the above cases, the shape - ID is invalid, and the namespace is inherited from the *current namespace*. - -The following example Smithy model contains comments above each member of -the shape named ``MyStructure`` that describes the shape the member resolves -to. - -.. code-block:: smithy - - namespace smithy.example - - use foo.baz#Bar - - string MyString - - structure MyStructure { - // Resolves to smithy.example#MyString - // There is a shape named MyString defined in the same namespace. - a: MyString, - - // Resolves to smithy.example#MyString - // Absolute shape IDs do not perform namespace resolution. - b: smithy.example#MyString, - - // Resolves to foo.baz#Bar - // The "use foo.baz#Bar" statement imported the Bar symbol, - // allowing the shape to be referenced using a relative shape ID. - c: Bar, - - // Resolves to foo.baz#Bar - // Absolute shape IDs do not perform namespace resolution. - d: foo.baz#Bar, - - // Resolves to foo.baz#MyString - // Absolute shape IDs do not perform namespace resolution. - e: foo.baz#MyString, - - // Resolves to smithy.api#String - // No shape named String was imported through a use statement - // the smithy.example namespace does not contain a shape named - // String, and the prelude model contains a shape named String. - f: String, - - // Resolves to smithy.example#MyBoolean. - // There is a shape named MyBoolean defined in the same namespace. - // Forward references are supported both within the same file and - // across multiple files. - g: MyBoolean, - - // Invalid. A shape by this name has not been imported through a - // use statement, a shape by this name does not exist in the - // current namespace, and a shape by this name does not exist in - // the prelude model. - h: InvalidShape, - } - - boolean MyBoolean - - ---------------- -Defining shapes ---------------- - -Shape statements are used to define shapes. - -.. productionlist:: smithy - shape_statements :*(`shape_statement` / `apply_statement`) - shape_statement :[`shape_documentation_comments` `ws`] - : `trait_statements` - : `shape_body` `br` - shape_documentation_comments :*(`documentation_comment`) - shape_body :`simple_shape_statement` - :/ `list_statement` - :/ `set_statement` - :/ `map_statement` - :/ `structure_statement` - :/ `union_statement` - :/ `service_statement` - :/ `operation_statement` - :/ `resource_statement` - -.. _shape-names: - -Shape names -=========== - -Consumers of a Smithy model MAY choose to inflect shape names, structure -member names, and other facets of a Smithy model in order to expose a more -idiomatic experience to particular programming languages. In order to make this -easier for consumers of a model, model authors SHOULD utilize a strict form of -PascalCase in which only the first letter of acronyms, abbreviations, and -initialisms are capitalized. - -=========== =============== -Recommended Not recommended -=========== =============== -UserId UserID -ResourceArn ResourceARN -IoChannel IOChannel -HtmlEntity HTMLEntity -HtmlEntity HTML_Entity -=========== =============== - - -.. _simple-types: - ------------- -Simple types ------------- - -*Simple types* are types that do not contain nested types or shape references. -Shapes that are simple types are defined using the following grammar: - -.. productionlist:: smithy - simple_shape_statement :`simple_type_name` `ws` `identifier` - simple_type_name :"blob" / "boolean" / "document" / "string" - :/ "byte" / "short" / "integer" / "long" - :/ "float" / "double" / "bigInteger" - :/ "bigDecimal" / "timestamp" - -.. list-table:: - :header-rows: 1 - :widths: 10 90 - - * - Type - - Description - * - blob - - Uninterpreted binary data - * - boolean - - Boolean value type - * - string - - UTF-8 encoded string - * - byte - - 8-bit signed integer ranging from -128 to 127 (inclusive) - * - short - - 16-bit signed integer ranging from -32,768 to 32,767 (inclusive) - * - integer - - 32-bit signed integer ranging from -2^31 to (2^31)-1 (inclusive) - * - long - - 64-bit signed integer ranging from -2^63 to (2^63)-1 (inclusive) - * - float - - Single precision IEEE-754 floating point number - * - double - - Double precision IEEE-754 floating point number - * - bigInteger - - Arbitrarily large signed integer - * - bigDecimal - - Arbitrary precision signed decimal number - * - timestamp - - Represents an instant in time with no UTC offset or timezone. The - serialization of a timestamp is determined by a - :ref:`protocol `. - * - document - - A protocol-specific untyped value. - -The following example defines a shape for each simple type in the -``smithy.example`` namespace: - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - blob Blob - boolean Boolean - string String - byte Byte - short Short - integer Integer - long Long - float Float - double Double - bigInteger BigInteger - bigDecimal BigDecimal - timestamp Timestamp - document Document - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#Blob": { - "type": "blob" - }, - "smithy.example#Boolean": { - "type": "boolean" - }, - "smithy.example#String": { - "type": "string" - }, - "smithy.example#Byte": { - "type": "byte" - }, - "smithy.example#Short": { - "type": "short" - }, - "smithy.example#Integer": { - "type": "integer" - }, - "smithy.example#Long": { - "type": "long" - }, - "smithy.example#Float": { - "type": "float" - }, - "smithy.example#Double": { - "type": "double" - }, - "smithy.example#BigInteger": { - "type": "bigInteger" - }, - "smithy.example#BigDecimal": { - "type": "bigDecimal" - }, - "smithy.example#Timestamp": { - "type": "timestamp" - }, - "smithy.example#Document": { - "type": "document" - } - } - } - -.. tip:: - - The :ref:`prelude model ` contains shapes for every simple type. - These shapes can be referenced using a relative shape ID - (for example, ``String``) or using an absolute shape ID - (for example, ``smithy.api#String``). - - -.. _timestamp-serialization-format: - -Timestamp serialization format -============================== - -By default, the serialization format of a timestamp is implicitly determined by -the :ref:`protocol ` of a service; however, the serialization -format can be explicitly configured in some protocols to override the default format -using the :ref:`timestampFormat-trait`. - -The following steps are taken to determine the serialization format of a -:ref:`member ` that targets a timestamp: - -1. Use the ``timestampFormat`` trait of the member, if present. -2. Use the ``timestampFormat`` trait of the shape, if present. -3. Use the default format of the protocol. - -The timestamp shape is an abstraction of time; the serialization format of a -timestamp as it is sent over the wire, whether determined by the protocol or by -the ``timestampFormat`` trait, SHOULD NOT have any effect on the types exposed -by tooling to represent a timestamp. - - -.. _document-type: - -Document types -============== - -A document type represents a protocol-specific untyped value. Documents -are useful when interacting with data that has no predefined schema, -uses a schema language that is not compatible with Smithy, or if the schema -that defines the data is specified and versioned outside of the -Smithy model. - -.. note:: - - * Not all protocols support document types - * The serialization format of a document is protocol-specific. - - -.. _aggregate-types: - ---------------- -Aggregate types ---------------- - -Aggregate types are types that are composed of other types. Aggregate shapes -reference other shapes using :ref:`members `. - -.. list-table:: - :header-rows: 1 - :widths: 10 90 - - * - Type - - Description - * - :ref:`member` - - Defined in aggregate types to reference other shapes - * - :ref:`list` - - homogeneous collection of values - * - :ref:`set` - - Unordered collection of unique homogeneous values - * - :ref:`map` - - Map data structure that maps string keys to homogeneous values - * - :ref:`structure` - - Fixed set of named heterogeneous members - * - :ref:`union` - - Tagged union data structure that can take on one of several - different, but fixed, types - - -.. _member: - -Member -====== - -:dfn:`Members` are defined in aggregate types to reference other shapes using -a :ref:`shape ID `. A member MUST NOT target an ``operation``, -``resource``, ``service``, ``member``, or :ref:`trait shapes `. - -The following example defines a list shape. The member of the list is a -member shape with a shape ID of ``smithy.example#MyList$member``. The member -targets the ``MyString`` shape in the same namespace. - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - list MyList { - member: MyString - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyList": { - "member": { - "target": "smithy.example#MyString" - } - } - } - } - -All shapes that contain members use following ABNF to define members: - -.. productionlist:: smithy - shape_members :`empty_shape_members` / `populated_shape_members` - empty_shape_members :"{" `ws` "}" - populated_shape_members :"{" `ws` `shape_member_kvp` - : *(`comma` `shape_member_kvp` `ws`) `trailing_comma` "}" - shape_member_kvp :[`shape_documentation_comments`] - : `trait_statements` - : `identifier` `ws` ":" `ws` `shape_id` - - -.. _list: - -List -==== - -The :dfn:`list` type represents a homogeneous collection of values. A list -statement requires that a :ref:`member ` named ``member`` is defined -in its body. Lists are defined using the following grammar: - -.. productionlist:: smithy - list_statement :"list" `ws` `identifier` `ws` `shape_members` - -The following example defines a list with a string member from the -:ref:`prelude `: - -.. tabs:: - - .. code-tab:: smithy - - list MyList { - member: String - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyList": { - "member": { - "target": "smithy.api#String" - } - } - } - } - -Traits can be applied to the list shape and its member: - -.. tabs:: - - .. code-tab:: smithy - - @length(min: 3, max: 10) - list MyList { - @length(min: 1, max: 100) - member: String - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyList": { - "member": { - "target": "smithy.api#String", - "traits": { - "smithy.api#length": { - "min": 1, - "max": 100 - } - } - }, - "traits": { - "smithy.api#length": { - "min": 3, - "max": 10 - } - } - } - } - } - - -.. _set: - -Set -=== - -The :dfn:`set` type represents an unordered collection of unique homogeneous -values. A set statement requires that a :ref:`member ` named -``member`` is defined in its body. Sets are defined using the following -grammar: - -.. productionlist:: smithy - set_statement :"set" `ws` `identifier` `ws` `shape_members` - -The following example defines a set of strings: - -.. tabs:: - - .. code-tab:: smithy - - set StringSet { - member: String - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#StringSet": { - "member": { - "target": "smithy.api#String" - } - } - } - } - -Traits can be applied to the set shape and its members: - -.. tabs:: - - .. code-tab:: smithy - - @deprecated - set StringSet { - @sensitive - member: String - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#StringSet": { - "member": { - "target": "smithy.api#String" - }, - "traits": { - "smithy.api#deprecated": {} - } - } - } - } - -.. note:: - - Not all languages support set data structures. Such languages SHOULD - represent sets as a custom set data structure that can interpret value - hash codes and equality, or alternatively, store the values of a set - data structure in a list and rely on validation to ensure uniqueness. - - -.. _map: - -Map -=== - -The :dfn:`map` type represents a map data structure that maps string keys to -homogeneous values. A map cannot contain duplicate keys. A map statement -requires that a ``key`` and ``value`` :ref:`member ` are defined in -its body. The ``key`` member of a map MUST target a ``string`` shape. - -Maps are defined using the following grammar: - -.. productionlist:: smithy - map_statement :"map" `ws` `identifier` `ws` `shape_members` - -The following example defines a map of strings to integers: - -.. tabs:: - - .. code-tab:: smithy - - map IntegerMap { - key: String, - value: Integer - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#IntegerMap": { - "key": { - "target": "smithy.api#String" - }, - "value": { - "target": "smithy.api#String" - } - } - } - } - -Traits can be applied to the map shape and its members: - -.. tabs:: - - .. code-tab:: smithy - - @length(min: 0, max: 100) - map IntegerMap { - @length(min: 1, max: 10) - key: String, - - @sensitive - value: Integer - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#IntegerMap": { - "key": { - "target": "smithy.api#String", - "traits": { - "smithy.api#length": { - "min": 1, - "max": 10 - } - } - }, - "value": { - "target": "smithy.api#String", - "traits": { - "smithy.api#sensitive": {} - } - }, - "traits": { - "smithy.api#length": { - "min": 0, - "max": 100 - } - } - } - } - } - - -.. _structure: - -Structure -========= - -The :dfn:`structure` type represents a fixed set of named, unordered, -heterogeneous members. A member name maps to exactly one structure -:ref:`member ` definition. A structure is defined using the -following grammar: - -.. productionlist:: smithy - structure_statement :"structure" `ws` `identifier` `ws` `shape_members` - -The following example defines a structure with two members: - -.. tabs:: - - .. code-tab:: smithy - - structure MyStructure { - foo: String, - baz: Integer, - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyStructure": { - "type": "structure", - "members": { - "foo": { - "target": "smithy.api#String" - }, - "baz": { - "target": "smithy.api#Integer" - } - } - } - } - } - -Traits can be applied to structure members: - -.. tabs:: - - .. code-tab:: smithy - - /// This is MyStructure. - structure MyStructure { - /// This is documentation for `foo`. - @required - foo: String, - - /// This is documentation for `baz`. - @deprecated - baz: Integer, - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyStructure": { - "type": "structure", - "members": { - "foo": { - "target": "smithy.api#String", - "traits": { - "smithy.api#documentation": "This is documentation for `foo`.", - "smithy.api#required": {} - } - }, - "baz": { - "target": "smithy.api#Integer", - "traits": { - "smithy.api#documentation": "This is documentation for `baz`.", - "smithy.api#deprecated": {} - } - } - }, - "traits": { - "smithy.api#documentation": "This is MyStructure." - } - } - } - } - -New members added to existing structures SHOULD be added to the end of the -structure. This ensures that programming languages that require a specific -data structure layout or alignment for code generated from Smithy models are -able to maintain backward compatibility. - - -.. _union: - -Union -===== - -The union type represents a `tagged union data structure`_ that can take -on several different, but fixed, types. Only one type can be used at any -one time. A union is defined using the following grammar: - -.. productionlist:: smithy - union_statement :"union" `ws` `identifier` `ws` `shape_members` - -The following example defines a union shape with several members: - -.. tabs:: - - .. code-tab:: smithy - - union MyUnion { - i32: Integer, - - stringA: String, - - @sensitive - stringB: String, - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyUnion": { - "type": "union", - "members": { - "i32": { - "target": "smithy.api#Integer" - }, - "stringA": { - "target": "smithy.api#String" - }, - "stringB": { - "target": "smithy.api#String", - "traits": { - "smithy.api#sensitive": {} - } - } - } - } - } - } - -New members added to existing unions SHOULD be added to the end of the -union. This ensures that programming languages that require a specific -data structure layout or alignment for code generated from Smithy models are -able to maintain backward compatibility. - - -.. _default-values: - -Default values -============== - -The values provided for :ref:`members ` of -:ref:`aggregate shapes ` are either always present and -set to a default value when necessary or *boxed*, meaning a value is -optionally present with no default value. - -- The default value of a ``byte``, ``short``, ``integer``, ``long``, - ``float``, and ``double`` shape that is not boxed is zero. -- The default value of a ``boolean`` shape that is not boxed is ``false``. -- All other shapes are always considered boxed and have no default value. - -Members are considered boxed if and only if the member is marked with the -:ref:`box-trait` or the shape targeted by the member is marked -with the box trait. Members that target strings, timestamps, and -aggregate shapes are always considered boxed and have no default values. - - -Recursive shape definitions -=========================== - -Smithy allows for recursive shape definitions with the following constraint: -the member of a list, set, or map cannot directly or transitively target its -containing shape unless one or more members in the path from the container -back to itself targets a structure or union shape. This ensures that shapes -that are typically impossible to define in various programming languages are -not defined in Smithy models (for example, you can't define a recursive list -in Java ``List` - - Entry point of an API that aggregates resources and operations together - * - :ref:`operation ` - - Represents the input, output and possible errors of an API operation - * - :ref:`resource ` - - Entity with an identity that has a set of operations - - -.. _service: - -Service -======= - -A :dfn:`service` is the entry point of an API that aggregates resources and -operations together. The :ref:`resources ` and -:ref:`operations ` of an API are bound within the closure of a -service. A service shape is defined by the following grammar: - -.. productionlist:: smithy - service_statement :"service" `ws` `identifier` `ws` `node_object` - -The service shape supports the following members: - -.. list-table:: - :header-rows: 1 - :widths: 10 20 70 - - * - Property - - Type - - Description - * - version - - ``string`` - - **Required**. Defines the version of the service. The version can be - provided in any format (e.g., ``2017-02-11``, ``2.0``, etc). - * - :ref:`operations ` - - [:ref:`shape-id`] - - Binds a set of ``operation`` shapes to the service. Each - element in the given list is a shape ID that MUST target an - :ref:`operation `. - * - :ref:`resources ` - - [:ref:`shape-id`] - - Binds a set of ``resource`` shapes to the service. Each element in - the given list is a shape ID that MUST target a - :ref:`resource `. - -The following example defines a service with no operations or resources. - -.. tabs:: - - .. code-tab:: smithy - - service MyService { - version: "2017-02-11" - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyService": { - "type": "service", - "version": "2017-02-11" - } - } - } - - -.. _service-operations: - -Service operations ------------------- - -:ref:`Operation ` shapes can be bound to a service by adding the -shape ID of an operation to the ``operations`` property of a service. -Operations bound directly to a service are typically RPC-style operations -that do not fit within a resource hierarchy. - -.. tabs:: - - .. code-tab:: smithy - - service MyService { - version: "2017-02-11", - operations: [GetServerTime], - } - - @readonly - operation GetServerTime { - output: GetServerTimeOutput - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyService": { - "type": "service", - "version": "2017-02-11", - "operations": [ - { - "target": "smithy.example#GetServerTime" - } - ] - }, - "smithy.example#GetServerTime": { - "type": "operation", - "output": { - "target": "smithy.example#GetServerTimeOutput" - } - } - } - } - - -.. _service-resources: - -Service resources ------------------ - -:ref:`Resource ` shapes can be bound to a service by adding the -shape ID of a resource to the ``resources`` property of a service. - -.. tabs:: - - .. code-tab:: smithy - - service MyService { - version: "2017-02-11", - resources: [MyResource], - } - - resource MyResource {} - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyService": { - "type": "service", - "version": "2017-02-11", - "resources": [ - { - "target": "smithy.example#MyResource" - } - ] - }, - "smithy.example#MyResource": { - "type": "resource" - } - } - } - - -.. _service-closure: - -Service closure ---------------- - -The *closure* of a service is the set of shapes connected to a service -through resources, operations, and members. - -.. important:: - - With some exceptions, the shapes that are referenced in the *closure* - of a service MUST have case-insensitively unique names regardless of - their namespace. - -By requiring unique names within a service, each service forms a -`ubiquitous language`_, making it easier for developers to understand the -model and artifacts generated from the model, like code. For example, when -using Java code generated from a Smithy model, a developer should not need -to discern between ``BadRequestException`` classes across multiple packages -that can be thrown by an operation. Uniqueness is required -case-insensitively because many model transformations (like code generation) -change the casing and inflection of shape names to make artifacts more -idiomatic. - -:ref:`Simple types ` and :ref:`lists ` or -:ref:`sets ` of compatible simple types are allowed to conflict because -a conflict for these type would rarely have an impact on generated artifacts. -These kinds of conflicts are only allowed if both conflicting shapes are the -same type and have the exact same traits. - -An operation or resource MUST NOT be bound to multiple shapes within the -closure of a service. This constraint allows services to discern between -operations and resources using only their shape name rather than a -fully-qualified path from the service to the shape. - - -.. _operation: - -Operation -========= - -The :dfn:`operation` type represents the input, output, and possible errors of -an API operation. Operation shapes are bound to :ref:`resource ` -shapes and :ref:`service ` shapes. An operation shape is defined by -the following grammar: - -.. productionlist:: smithy - operation_statement :"operation" `ws` `identifier` `ws` `node_object` - -An operation supports the following members: - -.. list-table:: - :header-rows: 1 - :widths: 10 90 - - * - Type - - Description - * - :ref:`input ` - - The optional input :ref:`structure ` of the operation. - * - :ref:`output ` - - The optional output :ref:`structure ` of the operation. - * - :ref:`errors ` - - Defines the errors 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 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 `. - -.. tabs:: - - .. code-tab:: smithy - - operation MyOperation { - input: Input, - output: Output, - errors: [NotFound, BadRequest] - } - - .. code-tab:: json - - { - "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" - } - ] - } - } - } - -.. _operation-input: - -Operation input ---------------- - -The input of an operation is an optional shape ID that MUST target a -structure shape. An operation is not required to accept input. - -The following example defines an operation that accepts an input structure -named ``Input``: - -.. tabs:: - - .. code-tab:: smithy - - operation MyOperation { - input: Input - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyOperation": { - "type": "operation", - "input": { - "target": "smithy.example#Input" - } - } - } - } - - -.. _operation-output: - -Operation output ----------------- - -The output of an operation is an optional shape ID that MUST target a -structure shape. An operation is not required to return output. - -The following example defines an operation that returns an output -structure named ``Output``: - -.. tabs:: - - .. code-tab:: smithy - - operation MyOperation { - output: Output - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyOperation": { - "type": "operation", - "output": { - "target": "smithy.example#Output" - } - } - } - } - - -.. _operation-errors: - -Operation errors ----------------- - -The optional ``errors`` property of an operation represent the errors that -can potentially occur when calling the operation. This property is a set -of shape IDs that MUST target :ref:`structure ` shapes that are -marked with the :ref:`error-trait`. - -The following example defines an operation shape that accepts no input, -returns no output, and can potentially return the -``NotFound`` or ``BadRequest`` errors. - -.. tabs:: - - .. code-tab:: smithy - - operation MyOperation { - errors: [NotFound, BadRequest] - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyOperation": { - "type": "operation", - "errors": [ - { - "target": "smithy.example#NotFound" - }, - { - "target": "smithy.example#BadRequest" - } - ] - } - } - } - - -.. _resource: - -Resource -======== - -Smithy defines a :dfn:`resource` as an entity with an identity that has a -set of operations. A resource shape is defined by the following grammar: - -.. productionlist:: smithy - resource_statement :"resource" `ws` `identifier` `ws` `node_object` - -A resource supports the following members: - -.. list-table:: - :header-rows: 1 - :widths: 10 30 60 - - * - Property - - Type - - Description - * - :ref:`identifiers ` - - Map - - Defines identifier names and shape IDs used to identify the resource. - * - :ref:`create ` - - :ref:`shape-id` - - Defines the lifecycle operation used to create a resource using one - or more identifiers created by the service. - * - :ref:`put ` - - :ref:`shape-id` - - Defines an idempotent lifecycle operation used to create a resource - using identifiers provided by the client. - * - :ref:`read ` - - :ref:`shape-id` - - Defines the lifecycle operation used to retrieve the resource. - * - :ref:`update ` - - :ref:`shape-id` - - Defines the lifecycle operation used to update the resource. - * - :ref:`delete ` - - :ref:`shape-id` - - Defines the lifecycle operation used to delete the resource. - * - :ref:`list ` - - :ref:`shape-id` - - Defines the lifecycle operation used to list resources of this type. - * - operations - - [:ref:`shape-id`] - - Binds a list of non-lifecycle instance operations to the resource. - * - collectionOperations - - [:ref:`shape-id`] - - Binds a list of non-lifecycle collection operations to the resource. - * - resources - - [:ref:`shape-id`] - - Binds a list of resources to this resource as a child resource, - forming a containment relationship. The resources MUST NOT have a - cyclical containment hierarchy, and a resource can not be bound more - than once in the entire closure of a resource or service. - - -.. _resource-identifiers: - -Identifiers ------------ - -:dfn:`Identifiers` are used to refer to a specific resource within a service. -The identifiers property of a resource is a map of identifier names to -:ref:`shape IDs ` that MUST target string shapes. - -For example, the following model defines a ``Forecast`` resource with a -single identifier named ``forecastId`` that targets the ``ForecastId`` shape: - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - resource Forecast { - identifiers: { - forecastId: ForecastId - } - } - - string ForecastId - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#Forecast": { - "type": "resource", - "identifiers": { - "forecastId": { - "target": "smithy.example#ForecastId" - } - } - }, - "smithy.example#ForecastId": { - "type": "string" - } - } - } - -When a resource is bound as a child to another resource using the "resources" -property, all of the identifiers of the parent resource MUST be repeated -verbatim in the child resource, and the child resource MAY introduce any -number of additional identifiers. - -:dfn:`Parent identifiers` are the identifiers of the parent of a resource. -All parent identifiers MUST be bound as identifiers in the input of every -operation bound as a child to a resource. :dfn:`Child identifiers` are the -identifiers that a child resource contains that are not present in the parent -identifiers. - -For example, given the following model, - -.. tabs:: - - .. code-tab:: smithy - - resource ResourceA { - identifiers: { - a: String - }, - resources: [ResourceB], - } - - resource ResourceB { - identifiers: { - a: String, - b: String, - }, - resources: [ResourceC], - } - - resource ResourceC { - identifiers: { - a: String, - b: String, - c: String, - } - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#ResourceA": { - "type": "resource", - "resources": [ - { - "target": "smithy.example#ResourceB" - } - ], - "identifiers": { - "a": { - "target": "smithy.api#String" - } - } - }, - "smithy.example#ResourceB": { - "type": "resource", - "resources": [ - { - "target": "smithy.example#ResourceC" - } - ], - "identifiers": { - "a": { - "target": "smithy.api#String" - }, - "b": { - "target": "smithy.api#String" - } - } - }, - "smithy.example#ResourceC": { - "type": "resource", - "identifiers": { - "a": { - "target": "smithy.api#String" - }, - "b": { - "target": "smithy.api#String" - }, - "c": { - "target": "smithy.api#String" - } - } - } - } - } - -``ResourceB`` is a valid child of ``ResourceA`` and contains a child -identifier of "b". ``ResourceC`` is a valid child of ``ResourceB`` and -contains a child identifier of "c". - -However, the following defines two *invalid* child resources that do not -define an ``identifiers`` property that is compatible with their parents: - -.. tabs:: - - .. code-tab:: smithy - - resource ResourceA { - identifiers: { - a: String, - b: String, - }, - resources: [Invalid1, Invalid2], - } - - resource Invalid1 { - // Invalid: missing "a". - identifiers: { - b: String, - }, - } - - resource Invalid2 { - identifiers: { - a: String, - // Invalid: does not target the same shape. - b: SomeOtherString, - }, - } - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#ResourceA": { - "type": "resource", - "identifiers": { - "a": { - "target": "smithy.api#String" - }, - "b": { - "target": "smithy.api#String" - } - }, - "resources": [ - { - "target": "smithy.example#Invalid1" - }, - { - "target": "smithy.example#Invalid2" - } - ] - }, - "smithy.example#Invalid1": { - "type": "resource", - "identifiers": { - "b": { - "target": "smithy.api#String" - } - } - }, - "smithy.example#Invalid2": { - "type": "resource", - "identifiers": { - "a": { - "target": "smithy.api#String" - }, - "b": { - "target": "smithy.example#SomeOtherString" - } - } - } - } - } - -.. _binding-identifiers: - -Binding identifiers to operations ---------------------------------- - -*Identifier bindings* indicate which top-level members of the input structure -of an operation provide values for the identifiers of a resource. - -**Validation** - -- Child resources MUST provide identifier bindings for all of its parent's - identifiers. -- Identifier bindings are only formed on input structure members that are - marked as :ref:`required `. -- Resource operations MUST form a valid *instance operation* or - *collection operation*. - -.. _instance-operations: - -:dfn:`Instance operations` are formed when all of the identifiers of a resource -are bound to the input structure of an operation or when a resource has no -identifiers. The :ref:`put `, :ref:`read `, -:ref:`update `, and :ref:`delete ` -lifecycle operations are examples of instance operations. An operation bound -to a resource using `operations` MUST form a valid instance operation. - -.. _collection-operations: - -:dfn:`Collection operations` are used when an operation is meant to operate on -a collection of resources rather than a specific resource. Collection -operations are formed when an operation is bound to a resource with `collectionOperations`, -or when bound to the :ref:`list ` or :ref:`create ` -lifecycle operations. A collection operation MUST omit one or more identifiers -of the resource it is bound to, but MUST bind all identifiers of any parent -resource. - - -.. _implicit-identifier-bindings: - -Implicit identifier bindings ----------------------------- - -*Implicit identifier bindings* are formed when the input of an operation -contains member names that target the same shapes that are defined in the -"identifiers" property of the resource to which an operation is bound. - -For example, given the following model, - -.. tabs:: - - .. code-tab:: smithy - - resource Forecast { - identifiers: { - forecastId: ForecastId, - }, - read: GetForecast, - } - - @readonly - operation GetForecast { - input: GetForecastInput, - output: GetForecastOutput - } - - structure GetForecastInput { - @required - forecastId: ForecastId, - } - - structure GetForecastOutput { - @required - weather: WeatherData, - } - - .. code-tab:: json - - { - "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": {} - } - } - } - } - } - } - -``GetForecast`` forms a valid instance operation because the operation is -not marked with the ``collection`` trait and ``GetForecastInput`` provides -*implicit identifier bindings* by defining a required "forecastId" member -that targets the same shape as the "forecastId" identifier of the resource. - -Implicit identifier bindings for collection operations are created in a -similar way to an instance operation, but MUST NOT contain identifier bindings -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 - } - - structure BatchPutForecastsInput { - @required - forecasts: BatchPutForecastList, - } - - .. code-tab:: json - - { - "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": {} - } - } - } - } - } - } - -``BatchPutForecasts`` forms a valid collection operation with implicit -identifier bindings because ``BatchPutForecastsInput`` does not require an -input member named "forecastId" that targets ``ForecastId``. - - -Explicit identifier bindings ----------------------------- - -*Explicit identifier bindings* are defined by applying the -:ref:`resourceIdentifier-trait` to a member of the input of for an -operation bound to a resource. Explicit bindings are necessary when the name of -the input structure member differs from the name of the resource identifier to -which the input member corresponds. - -For example, given the following, - -.. code-block:: smithy - - resource Forecast { - // continued from above - resources: [HistoricalForecast], - } - - resource HistoricalForecast { - identifiers: { - forecastId: ForecastId, - historicalId: HistoricalForecastId, - }, - read: GetHistoricalForecast, - list: ListHistoricalForecasts, - } - - @readonly - operation GetHistoricalForecast { - input: GetHistoricalForecastInput, - output: GetHistoricalForecastOutput - } - - structure GetHistoricalForecastInput { - @required - @resourceIdentifier("forecastId") - customForecastIdName: ForecastId, - - @required - @resourceIdentifier("historicalId") - customHistoricalIdName: String - } - -the :ref:`resourceIdentifier-trait` on ``GetHistoricalForecastInput$customForecastIdName`` -maps it to the "forecastId" identifier is provided by the -"customForecastIdName" member, and the :ref:`resourceIdentifier-trait` -on ``GetHistoricalForecastInput$customHistoricalIdName`` maps that member -to the "historicalId" identifier. - - -.. _lifecycle-operations: - -Lifecycle operations --------------------- - -:dfn:`Lifecycle operations` are used to transition the state of a resource -using well-defined semantics. Lifecycle operations are defined by setting the -``put``, ``create``, ``read``, ``update``, ``delete``, and ``list`` properties -of a resource to target an operation shape. - -The following snippet defines a resource with each lifecycle method: - -.. code-block:: smithy - - resource Forecast { - identifiers: { - forecastId: ForecastId, - }, - put: PutForecast, - create: CreateForecast, - read: GetForecast, - update: UpdateForecast, - delete: DeleteForecast, - list: ListForecasts, - } - - -.. _put-lifecycle: - -Put lifecycle -------------- - -The ``put`` lifecycle operation is used to create a resource using identifiers -provided by the client. - -**Validation** - -- Put operations MUST NOT be marked with :ref:`readonly-trait`. -- Put operations MUST be marked with :ref:`idempotent-trait`. -- Put operations MUST form valid :ref:`instance operations `. - -The following snippet defines the ``PutForecast`` operation. - -.. code-block:: smithy - - operation PutForecast { - input: PutForecastInput, - output: PutForecastOutput - } - - @idempotent - structure PutForecastInput { - // The client provides the resource identifier. - @required - forecastId: ForecastId, - - chanceOfRain: Float - } - -The semantics of a ``put`` lifecycle operation are similar to the semantics -of an `HTTP PUT method`_: - - The PUT method requests that the state of the target resource be - created or replaced ... - -The :ref:`noReplace-trait` can be applied to resources that define a -``put`` lifecycle operation to indicate that a resource cannot be -replaced using the ``put`` operation. - - -.. _create-lifecycle: - -Create lifecycle ----------------- - -The ``create`` operation is used to create a resource using one or more -identifiers created by the service. - -**Validation** - -- Create operations MUST NOT be marked with :ref:`readonly-trait`. -- Create operations MUST form valid :ref:`collection operations `. -- The ``create`` operation MAY be marked with :ref:`idempotent-trait`. - -The following snippet defines the ``CreateForecast`` operation. - -.. code-block:: smithy - - operation CreateForecast { - input: CreateForecastInput, - output: CreateForecastOutput - } - - operation CreateForecast { - input: CreateForecastInput, - output: CreateForecastOutput - } - - structure CreateForecastInput { - // No identifier is provided by the client, so the service is - // responsible for providing the identifier of the resource. - chanceOfRain: Float, - } - - -.. _read-lifecycle: - -Read lifecycle --------------- - -The ``read`` operation is the canonical operation used to retrieve the current -representation of a resource. - -**Validation** - -- Read operations MUST be valid :ref:`instance operations `. -- Read operations MUST be marked with :ref:`readonly-trait`. - -For example: - -.. code-block:: smithy - - @readonly - operation GetForecast { - input: GetForecastInput, - output: GetForecastOutput, - errors: [ResourceNotFound] - } - - structure GetForecastInput { - @required - forecastId: ForecastId, - } - - -.. _update-lifecycle: - -Update lifecycle ----------------- - -The ``update`` operation is the canonical operation used to update a -resource. - -**Validation** - -- Update operations MUST be valid :ref:`instance operations `. -- Update operations MUST NOT be marked with :ref:`readonly-trait`. - -For example: - -.. code-block:: smithy - - operation UpdateForecast { - input: UpdateForecastInput, - output: UpdateForecastOutput, - errors: [ResourceNotFound] - } - - structure UpdateForecastInput { - @required - forecastId: ForecastId, - - chanceOfRain: Float, - } - - -.. _delete-lifecycle: - -Delete lifecycle ----------------- - -The ``delete`` operation is canonical operation used to delete a resource. - -**Validation** - -- Delete operations MUST be valid :ref:`instance operations `. -- Delete operations MUST NOT be marked with :ref:`readonly-trait`. -- Delete operations MUST be marked with :ref:`idempotent-trait`. - -For example: - -.. code-block:: smithy - - @idempotent - operation DeleteForecast { - input: DeleteForecastInput, - output: DeleteForecastOutput, - errors: [ResourceNotFound] - } - - structure DeleteForecastInput { - @required - forecastId: ForecastId, - } - - -.. _list-lifecycle: - -List lifecycle --------------- - -The ``list`` operation is the canonical operation used to list a -collection of resources. - -**Validation** - -- List operations MUST form valid :ref:`collection operations `. -- List operations MUST be marked with :ref:`readonly-trait`. -- The output of a list operation SHOULD contain references to the resource - being listed. -- List operations SHOULD be :ref:`paginated `. - -For example: - -.. code-block:: smithy - - @readonly @paginated - operation ListForecasts { - input: ListForecastsInput, - output: ListForecastsOutput - } - - structure ListForecastsInput { - maxResults: Integer, - nextToken: String - } - - structure ListForecastsOutput { - nextToken: String, - @required - forecasts: ForecastList - } - - list ForecastList { - member: ForecastId - } - - -.. _referencing-resources: - -Referencing resources ---------------------- - -References between resources can be defined in a Smithy model at design-time. -Resource references allow tooling to understand the relationships between -resources and how to dereference the location of a resource. - -A reference to a resource is formed when the :ref:`references-trait` -is applied to a structure or string shape. The following example creates a -reference to a ``HistoricalForecast`` resource (a resource that requires the -"forecastId" and "historicalId" identifiers): - -.. code-block:: smithy - - @references([{resource: HistoricalForecast}]) - structure HistoricalReference { - forecastId: ForecastId, - historicalId: HistoricalForecastId - } - -Notice that in the above example, the identifiers of the resource were not -explicitly mapped to structure members. This is because the targeted structure -contains members with names that match the names of the identifiers of the -``HistoricalForecast`` resource. - -Explicit mappings between identifier names and structure member names can be -defined if needed. For example: - -.. code-block:: smithy - - @references([ - { - resource: HistoricalForecast, - ids: { - forecastId: "customForecastId", - historicalId: "customHistoricalId" - } - } - ]) - structure AnotherHistoricalReference { - customForecastId: String, - customHistoricalId: String, - } - -A reference can be formed on a string shape for resources that have one -identifier. References applied to a string shape MUST omit the "ids" -property in the reference. - -.. code-block:: smithy - - resource SimpleResource { - identifiers: { - foo: String, - } - } - - @references([{resource: SimpleResource}]) - string SimpleResourceReference - -See the :ref:`references-trait` for more information about references. - -.. _tagged union data structure: https://en.wikipedia.org/wiki/Tagged_union -.. _ubiquitous language: https://martinfowler.com/bliki/UbiquitousLanguage.html -.. _HTTP PUT method: https://tools.ietf.org/html/rfc7231#section-4.3.4 diff --git a/docs/source/1.0/spec/core/traits.rst b/docs/source/1.0/spec/core/traits.rst deleted file mode 100644 index 507735c4484..00000000000 --- a/docs/source/1.0/spec/core/traits.rst +++ /dev/null @@ -1,694 +0,0 @@ -.. _traits: - -====== -Traits -====== - -*Traits* are model components that can be attached to :doc:`shapes ` -to describe additional information about the shape; shapes provide the -structure and layout of an API, while traits provide refinement and style. - -.. contents:: Table of contents - :depth: 1 - :local: - :backlinks: none - - -------------------------- -Applying traits to shapes -------------------------- - -Trait values immediately preceding a shape definition are applied to the -shape. - -The following example applies the ``sensitive`` and ``documentation`` trait -to ``MyString``: - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - @sensitive - @documentation("Contains a string") - string MyString - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyString": { - "type": "string", - "traits": { - "smithy.api#documentation": "Contains a string", - "smithy.api#sensitive": {} - } - } - } - } - -The shape ID of a trait is *resolved* against :token:`use_statement`\s and the -current namespace in exactly the same same way as -:ref:`other shape IDs `. - -.. important:: - - Trait names are case-sensitive; it is invalid, for example, to refer to - the :ref:`documentation-trait` as "Documentation"). - - -.. _apply-statement: - -Apply statement -=============== - -Traits can be applied to shapes outside of a shape's definition using the -``apply`` statement. This can be useful for allowing different teams within -the same organization to independently own different facets of a model. -For example, a service team could own the Smithy model that defines the -shapes and traits of the API, and a documentation team could own a Smithy -model that applies documentation traits to the shapes. - -Apply statements are formed using the following grammar: - -.. productionlist:: smithy - apply_statement :"apply" `ws` `shape_id` `ws` `trait` `br` - -The following example applies the :ref:`documentation-trait` and -:ref:`length-trait` to the ``smithy.example#MyString`` shape: - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - apply MyString @documentation("This is my string!") - apply MyString @length(min: 1, max: 10) - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyString": { - "type": "apply", - "traits": { - "smithy.api#documentation": "This is my string!", - "smithy.api#length": { - "min": 1, - "max": 10 - } - } - } - } - } - -Traits can be applied to members too: - -.. code-block:: smithy - - namespace smithy.example - - apply MyStructure$foo @documentation("Structure member documentation") - apply MyUnion$foo @documentation("Union member documentation") - apply MyList$member @documentation("List member documentation") - apply MySet$member @documentation("Set member documentation") - apply MyMap$key @documentation("Map key documentation") - apply MyMap$value @documentation("Map key documentation") - - -Scope of member traits -====================== - -Traits that target :ref:`members ` apply only in the context of -the member shape and do not affect the shape targeted by the member. Traits -applied to a member supersede traits applied to the shape referenced by the -member and do not conflict. - - -.. _trait-values: - ------------- -Trait values ------------- - -The value that can be provided for a trait depends on its type. A value for a -trait is defined in the IDL by enclosing the value in parenthesis. Trait values -can only appear immediately before a shape. - -.. productionlist:: smithy - trait_statements : *(`ws` `trait`) `ws` - trait :"@" `shape_id` [`trait_body`] - trait_body :"(" `ws` `trait_body_value` `ws` ")" - trait_body_value :`trait_structure` / `node_value` - trait_structure :`trait_structure_kvp` *(`ws` `comma` `trait_structure_kvp`) - trait_structure_kvp :`node_object_key` `ws` ":" `ws` `node_value` - -The following example applies various traits to a structure shape and its -members. - -.. code-block:: smithy - - @documentation("An animal in the animal kingdom") - structure Animal { - @required - name: smithy.api#String, - - @length(min: 0) - age: smithy.api#Integer, - } - - -Structure, map, and union trait values -====================================== - -Traits that are a ``structure``, ``union``, or ``map`` are defined using -a special syntax that places key-value pairs inside of the trait -parenthesis. Wrapping braces, "{" and "}", are not permitted. - -.. code-block:: smithy - :caption: Example - - @structuredTrait(foo: "bar", baz: "bam") - -Nested structure, map, and union values are defined using -:ref:`node value ` productions: - -.. code-block:: smithy - - @structuredTrait( - foo: { - bar: "baz", - qux: "true", - } - ) - -Omitting a value is allowed on ``list``, ``set``, ``map``, and ``structure`` -traits if the shapes have no ``length`` constraints or ``required`` members. - - -Annotation traits -================= - -A structure trait with no members is called an *annotation trait*. It's hard -to predict what information a trait needs to capture when modeling a domain; -a trait might start out as a simple annotation, but later might benefit -from additional information. By defining an annotation trait rather than a -boolean trait, the trait can safely add optional members over time as needed. - -The following example defines an annotation trait named ``foo``: - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - @trait - structure foo {} - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#foo": { - "type": "structure", - "traits": { - "smithy.api#trait": {} - } - } - } - } - -The following applications of the ``foo`` annotation trait are equivalent: - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - @foo - string MyString1 - - @foo() - string MyString2 - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyString1": { - "type": "string", - "traits": { - "smithy.api#foo": {} - } - }, - "smithy.example#MyString2": { - "type": "string", - "traits": { - "smithy.api#foo": {} - } - } - } - } - -A member can be safely added to an annotation trait if the member is not -marked as :ref:`required `. The applications of the ``foo`` -trait in the previous example and the following example are all valid even -after adding a member to the ``foo`` trait: - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - @trait - structure foo { - baz: String, - } - - @foo(baz: "bar") - string MyString4 - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#foo": { - "type": "structure", - "members": { - "baz": { - "target": "smithy.api#String" - } - }, - "traits": { - "smithy.api#trait": {} - } - }, - "smithy.example#MyString4": { - "type": "string", - "traits": { - "smithy.api#foo": { - "baz": "bar" - } - } - } - } - } - - -List and set trait values -========================= - -Traits that are a ``list`` or ``set`` shape are defined inside -of brackets (``[``) and (``]``) using a :token:`node_array` production. - -.. code-block:: smithy - :caption: Example - - @tags(["a", "b"]) - - -Other trait values -================== - -All other trait values MUST adhere to the JSON type mappings defined -in :ref:`trait-node-values` table. - -The following example defines a string trait value: - -.. code-block:: smithy - - @documentation("Hello") - - -.. _trait-shapes: - ---------------------- -Defining trait shapes ---------------------- - -A *trait shape* is a shape that is specialized to function as a trait. -Traits are defined inside of a namespace by applying ``smithy.api#trait`` -to a shape. This trait can only be applied to simple types, ``list``, -``map``, ``set``, ``structure``, and ``union`` shapes. - -The following example defines a trait named ``myTraitName`` in the -``smithy.example`` namespace: - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - @trait(selector: "*") - structure myTraitName {} - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#myTraitName": { - "type": "structure", - "traits": { - "smithy.api#trait": { - "selector": "*" - } - } - } - } - } - -.. tip:: - - By convention, trait shape names SHOULD use a lowercase name so that they - visually stand out from normal shapes. - -After a trait is defined, it can be applied to any shape that matches its -selector. The following example applies the ``smithy.example#myTraitName`` -trait to the ``MyString`` shape using a trait shape ID that is relative to -the current namespace: - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - @myTraitName - string MyString - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#MyString": { - "type": "string", - "traits": { - "smithy.api#myTraitName": {} - } - } - } - } - -Built-in traits defined in the Smithy :ref:`prelude ` namespace, -``smithy.api``, are automatically available in every Smithy model and -namespace through relative shape IDs. - -.. important:: - - The only valid reference to a trait shape is through applying the trait - to a shape. Members and references within a model MUST NOT refer to - trait shapes. - - -.. _trait-shape-properties: - -Trait shape properties -====================== - -The ``smithy.api#trait`` trait is a structure that supports the following -members: - -.. list-table:: - :header-rows: 1 - :widths: 10 20 70 - - * - Property - - Type - - Description - * - selector - - string - - A valid :ref:`selector ` that defines where the trait - can be applied. For example, a ``selector`` set to ``:test(list, map)`` - means that the trait can be applied to a :ref:`list` or :ref:`map` - shape. This value defaults to ``*`` if not set, meaning the trait can - be applied to any shape. - * - conflicts - - [string] - - Defines the shape IDs of traits that MUST NOT be applied to the same - shape as the trait being defined. This allows traits to be defined as - mutually exclusive. Relative shape IDs that are not resolved in the IDL - while parsing are assumed to refer to traits defined in the prelude - namespace, ``smithy.api``. Conflict shape IDs MAY reference unknown - trait shapes that are not defined in the model. - * - structurallyExclusive - - string - - One of "member" or "target". When set to "member", only a single - member of a structure can be marked with the trait. When set to - "target", only a single member of a structure can target a shape - marked with this trait. - -The following example defines two custom traits: ``beta`` and -``structuredTrait``: - -.. tabs:: - - .. code-tab:: smithy - - namespace smithy.example - - /// A trait that can be applied to a member. - @trait(selector: "structure > member") - structure beta {} - - /// A trait that has members. - @trait(selector: "string", conflicts: [beta]) - structure structuredTrait { - @required - lorem: StringShape, - - @required - ipsum: StringShape, - - dolor: StringShape, - } - - // Apply the "beta" trait to the "foo" member. - structure MyShape { - @required - @beta - foo: StringShape, - } - - // Apply the structuredTrait to the string. - @structuredTrait( - lorem: "This is a custom trait!", - ipsum: "lorem and ipsum are both required values.") - string StringShape - - .. code-tab:: json - - { - "smithy": "1.0", - "shapes": { - "smithy.example#beta": { - "type": "apply", - "traits": { - "smithy.api#type": "structure", - "smithy.api#trait": { - "selector": "structure > member" - }, - "smithy.api#documentation": "A trait that can be applied to a member." - } - }, - "smithy.example#structuredTrait": { - "type": "apply", - "traits": { - "smithy.api#type": "structure", - "smithy.api#trait": { - "selector": "string", - "conflicts": [ - "smithy.example#beta" - ] - }, - "smithy.api#members": { - "lorem": { - "target": "StringShape", - "required": true - }, - "dolor": { - "target": "StringShape" - } - }, - "smithy.api#documentation": "A trait that has members." - } - }, - "smithy.example#MyShape": { - "type": "apply", - "traits": { - "smithy.api#type": "structure", - "smithy.api#members": { - "beta": { - "target": "StringShape", - "required": true, - "beta": true - } - } - } - }, - "smithy.example#StringShape": { - "type": "apply", - "traits": { - "smithy.api#type": "string", - "smithy.api#structuredTrait": { - "lorem": "This is a custom trait!", - "ipsum": "lorem and ipsum are both required values." - } - } - } - } - } - - -.. _trait-node-values: - ------------------ -Trait node values ------------------ - -The value provided for a trait MUST be compatible with the ``shape`` defined -for the trait. The following table defines each shape type that is available -to target from trait shapes and how values for those shapes are defined -in JSON and :token:`node ` values. - -.. list-table:: - :header-rows: 1 - :widths: 20 20 60 - - * - Smithy type - - JSON type - - Description - * - blob - - string - - A ``string`` value that is base64 encoded. - * - boolean - - boolean - - Can be set to ``true`` or ``false``. - * - byte - - number - - The value MUST fall within the range of -128 to 127 - * - short - - number - - The value MUST fall within the range of -32,768 to 32,767 - * - integer - - number - - The value MUST fall within the range of -2^31 to (2^31)-1. - * - long - - number - - The value MUST fall within the range of -2^63 to (2^63)-1. - * - float - - number - - A normal JSON number. - * - double - - number - - A normal JSON number. - * - bigDecimal - - string | number - - bigDecimal values can be serialized as strings to avoid rounding - issues when parsing a Smithy model in various languages. - * - bigInteger - - string | number - - bigInteger values can be serialized as strings to avoid truncation - issues when parsing a Smithy model in various languages. - * - string - - string - - The provided value SHOULD be compatible with the ``mediaType`` of the - string shape if present; however, this is not validated by Smithy. - * - timestamp - - number | string - - If a number is provided, it represents Unix epoch seconds with optional - millisecond precision. If a string is provided, it MUST be a valid - :rfc:`3339` string with optional millisecond precision and no - UTC offset (for example, ``1990-12-31T23:59:60Z``). - * - list and set - - array - - Each value in the array MUST be compatible with the referenced member. - * - map - - object - - Each key MUST be compatible with the ``key`` member of the map, and - each value MUST be compatible with the ``value`` member of the map. - * - structure - - object - - All members marked as required MUST be provided in a corresponding - key-value pair. Each key MUST correspond to a single member name of - the structure. Each value MUST be compatible with the member that - corresponds to the member name. - * - union - - object - - The object MUST contain a single single key-value pair. The key MUST be - one of the member names of the union shape, and the value MUST be - compatible with the corresponding shape. - -Trait values MUST be compatible with any constraint traits found related to the -shape being validated. - - -.. _trait-conflict-resolution: - -------------------------- -Trait conflict resolution -------------------------- - -Trait conflict resolution is used when the same trait is applied multiple -times to a shape. Duplicate traits applied to shapes are allowed in the -following cases: - -1. If the trait is a ``list`` or ``set``, then the conflicting trait values - are concatenated into a single trait value. -2. If both values are exactly equal, then the conflict is ignored. - -All other instances of trait collisions are prohibited. - -The following model definition is **valid** because the ``length`` trait is -duplicated on the ``MyList`` shape with the same values: - -.. code-block:: smithy - - namespace smithy.example - - @length(min: 0, max: 10) - list MyList { - member: String - } - - apply MyList @length(min: 0, max: 10) - -The following model definition is **valid** because the ``tags`` trait is -a :ref:`list` shape: - -.. code-block:: smithy - - namespace smithy.example - - @tags(["foo", "baz", "bar"]) - string MyString - - // This is a valid trait collision on a list trait, tags. - // tags becomes ["foo", "baz", "bar", "bar", "qux"] - apply MyString @tags(["bar", "qux"]) - -The following model definition is **invalid** because the ``length`` trait is -duplicated on the ``MyList`` shape with different values: - -.. code-block:: smithy - - namespace smithy.example - - @length(min: 0, max: 10) - list MyList { - member: String - } - - apply MyList @length(min: 10, max: 20) 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 137d55a18e9..a7b405c2df9 100644 --- a/docs/source/1.0/spec/core/type-refinement-traits.rst +++ b/docs/source/1.0/spec/core/type-refinement-traits.rst @@ -74,7 +74,7 @@ used in all Smithy models, including boxed and unboxed shapes. Summary Indicates that a structure shape represents an error. All shapes - referenced by the :ref:`errors list of an operation ` + referenced by the :ref:`errors list of an operation ` MUST be targeted with this trait. Trait selector ``structure`` diff --git a/docs/source/1.0/spec/index.rst b/docs/source/1.0/spec/index.rst index 9f7ad0930ef..cc5ee4963a4 100644 --- a/docs/source/1.0/spec/index.rst +++ b/docs/source/1.0/spec/index.rst @@ -14,8 +14,7 @@ Smithy specification -------------------- .. toctree:: - :maxdepth: 2 - :caption: Defines the core Smithy specification. Start here. + :maxdepth: 3 core/index @@ -28,7 +27,6 @@ Additional specifications .. toctree:: :maxdepth: 1 - :caption: Defines additional specifications. http-protocol-compliance-tests mqtt @@ -42,6 +40,5 @@ AWS specifications .. toctree:: :maxdepth: 2 - :caption: Defines AWS specifications. aws/index diff --git a/docs/source/conf.py b/docs/source/conf.py index 6e5c2cacd88..dc52b2bcaae 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,6 +26,8 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx_tabs.tabs', + # We use redirects to be able to change page names. + 'sphinxcontrib.redirects', 'smithy'] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index dbe2a4f4361..17f49492fd8 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -68,7 +68,7 @@ Smithy supports the following types: - An instant in time with no UTC offset or timezone. The serialization of a timestamp is determined by a :ref:`protocol `. - * - :ref:`document ` + * - document type - An untyped JSON-like value. * - :ref:`list` - Homogeneous collection of values @@ -132,7 +132,7 @@ weather service. .. admonition:: What's that syntax? :class: note - Smithy models are defined using either the :ref:`Smithy IDL ` + Smithy models are defined using either the :ref:`Smithy IDL ` or the :ref:`JSON AST `. The JSON AST representation of a model is typically an artifact created by build tools to make them easier to use by other tooling. @@ -652,7 +652,7 @@ calls to retrieve the entire list of results. It's usually a good idea to add pagination to an API that lists resources because it can help prevent operational issues in the future if the list grows to an unpredicted size. -The ``CitySummary`` structure defines a :ref:`reference ` +The ``CitySummary`` structure defines a :ref:`reference ` to a ``City`` resource. This gives tooling a better understanding of the relationships in your service. diff --git a/docs/source/redirects b/docs/source/redirects new file mode 100644 index 00000000000..066d8a9841b --- /dev/null +++ b/docs/source/redirects @@ -0,0 +1,6 @@ +1.0/spec/core/intro.rst 1.0/spec/core/model.rst +1.0/spec/core/shapes.rst 1.0/spec/core/model.rst +1.0/spec/core/lexical-structure.rst 1.0/spec/core/model.rst +1.0/spec/core/model-metadata.rst 1.0/spec/core/model.rst +1.0/spec/core/merging-models.rst 1.0/spec/core/model.rst +1.0/spec/core/traits.rst 1.0/spec/core/model.rst diff --git a/docs/themes/smithy/static/bootstrap-reboot.css b/docs/themes/smithy/static/bootstrap-reboot.css index 17212682b1a..048bf49f19b 100644 --- a/docs/themes/smithy/static/bootstrap-reboot.css +++ b/docs/themes/smithy/static/bootstrap-reboot.css @@ -105,7 +105,7 @@ dfn { b, strong { - font-weight: bolder; + font-weight: bold; } small { diff --git a/docs/themes/smithy/static/default.css_t b/docs/themes/smithy/static/default.css_t index 27e2f291650..09eb6f5352c 100644 --- a/docs/themes/smithy/static/default.css_t +++ b/docs/themes/smithy/static/default.css_t @@ -45,22 +45,63 @@ blockquote { padding: 0 1em; } -.figure { +/* text-figure looks just like figures */ + +.figure, +.text-figure { background-color: #eee; border-radius: 4px; padding: 3px; margin-bottom: 1.5em; } -.figure img { +.figure img, +.text-figure pre { display: block; background-color: #fff; + margin-bottom: 0; } .figure .caption { padding: 1em 1em 0 1em; } +.text-figure .code-block-caption { + padding: 0.5em; + text-align: center; + font-size: 85%; +} + +/* ----- Anchors ------ */ + +a { + color: {{ theme_link_color }}; + text-decoration: none; +} + +a:hover, .reference.external:hover { + text-decoration: underline; + color: {{ theme_link_color }}; +} + +.reference.external { + text-decoration: underline dotted #ccc; +} + +.headerlink { + visibility: none; + margin-left: 1em; + font-size: 18px; + vertical-align: super; + line-height: 0; + opacity: 0; + transition: opacity .25s; +} + +*:hover > .headerlink { + opacity: 1; +} + /* ----- Headings ------ */ h1, h2, h3, h4, h5, h6 { @@ -74,8 +115,12 @@ h1 a, h2 a, h3 a, h4 a, h5 a, h6 a, h7 a { color: #000; } +h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover, h7 a:hover { + text-decoration: none; +} + h1, h2, h3, h4, h5, h6, h7 { - border-bottom: 1px solid #ccc; + border-bottom: 1px solid #aaa; padding-bottom: 0.3em; } @@ -133,7 +178,7 @@ h7 { } #splash h1 { - color: #dda15e; + color: #fff; padding: 0; margin: 0 0 1rem; font-size: 1.7em; @@ -304,37 +349,6 @@ hr { } -/* ----- Anchors ------ */ - -a { - color: {{ theme_link_color }}; - text-decoration: none; -} - -a:hover, .reference.external:hover { - text-decoration: underline; - color: {{ theme_link_color }}; -} - -.reference.external { - text-decoration: underline dotted #ccc; -} - -.headerlink { - visibility: none; - margin-left: 1em; - font-size: 18px; - vertical-align: super; - line-height: 0; - opacity: 0; - transition: opacity .25s; -} - -*:hover > .headerlink { - opacity: 1; -} - - /* ----- Header and footer ------ */ header { @@ -568,6 +582,11 @@ pre a code.xref { font-size: 100%; } +.text-figure pre { + line-height: 1; + font-size: 70%; +} + .productionlist strong { font-size: 1.15em; } @@ -612,7 +631,6 @@ table.highlighttable pre { } .code-block-caption .caption-text { - font-style: italic; color: #666; } @@ -779,7 +797,7 @@ table.highlighttable pre { .admonition { margin: 20px 0; - padding: 0 2em; + padding: 0 0 0 1em; border-width: 0 0 0 6px; border-style: solid; border-color: #ccc; @@ -795,7 +813,7 @@ table.highlighttable pre { .admonition-title { margin: 0; - padding: 0 0.5em 0 0; + padding: 0 8px 0 0; font-size: 1em; font-weight: bold; color: rgba(0, 0, 0, 0.6); @@ -803,7 +821,15 @@ table.highlighttable pre { } .admonition-title:after { - content: ": "; + content: ":"; +} + +.admonition-title + ul, +.admonition-title + ol, +.admonition-title + pre, +.admonition-title + div, +.admonition-title + dl { + clear: left; } .admonition.danger, @@ -828,7 +854,7 @@ table.highlighttable pre { } div.seealso { - border: none; + border-color: #ccc; } div.admonition tt.xref, div.admonition a tt { @@ -940,7 +966,6 @@ dt:target, .highlighted { } .caption-text { - font-weight: 300; color: #222; } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/TraitDefinition.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/TraitDefinition.java index e4a3c3382a4..45c3bad61a9 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/traits/TraitDefinition.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/TraitDefinition.java @@ -22,7 +22,6 @@ import java.util.Locale; import java.util.Objects; import java.util.Optional; -import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; @@ -158,12 +157,6 @@ public Builder selector(Selector selector) { public Builder addConflict(String trait) { Objects.requireNonNull(trait); - - // Use absolute trait names. - if (!trait.contains("#")) { - trait = Prelude.NAMESPACE + "#" + trait; - } - return addConflict(ShapeId.from(trait)); } diff --git a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy index 71cf343c316..89aa0ceee57 100644 --- a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy +++ b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy @@ -219,7 +219,7 @@ string jsonName /// Serializes an object property as an XML attribute rather than a nested XML element. @trait(selector: "structure > :test(member > :test(boolean, number, string, timestamp))", - conflicts: ["xmlNamespace"]) + conflicts: [xmlNamespace]) @tags(["diff.error.const"]) structure xmlAttribute {} @@ -235,7 +235,7 @@ structure xmlFlattened {} string xmlName /// Adds an xmlns namespace definition URI to an XML element. -@trait(conflicts: ["xmlAttribute"]) +@trait(conflicts: [xmlAttribute]) @tags(["diff.error.const"]) structure xmlNamespace { /// The namespace URI for scoping this XML element.