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.