Skip to content

Commit

Permalink
Add support for custom default values
Browse files Browse the repository at this point in the history
This commit updates Smithy IDL v2 to support custom default values
rather than only default zero values.

We didn't previoulsy support custom default values because we wanted to
be able to change default values if ever needed, we wanted to avoid
information disclosure of unreleased internal members, and we wanted
clients to be able to fill in a default zero value if a required member
is missing on the wire (implying that the member transitioned from
required to default).

While default zero values are a big simplification for clients, they do
come with awkward tradeoffs. For example, the zero value of an enum was
`""` (or it could have been the first enum in the shape definition but
that's problematic due to model filtering, and it requires a lot of
up-front planning from service teams). We also observed that there are
likely 1,000+ members already used in AWS that have default values that
are only captured through documentation. If we could actually model
defaults, it would be easier to catch changes to default values in
automated diff tools and discuss them in API reviews.

To address these concerns and support custom default values, we
recommend the following:

* clients implement presence tracking and only serialize default values if
  the member is explicitly set to the default value. This allows services
  to change defaults if ever necessary.
* servers always serialize default values unless the member is marked with
  the `@internal` trait. This makes it explicit as to what the server
  thinks the default value is while still preventing unintended
  information disclosure of unreleased features.
* To avoid downtime and account for misconfigured servers that do not
  serialize required or default members, clients attempt to populate the
  member with a default zero value.

More details and specifics can be found in
defaults-and-model-evolution.md.

Enum changes:
* Given the addition of syntax sugar for assigning values to
  defaulted structure members, this commit also allows syntactic sugar for
  assigning values to enum and intEnum shapes without the use of the
  `@enumValue` trait.
* Now that there is no default zero value for enums, the `@enumDefault`
  trait has been removed.
  • Loading branch information
mtdowling committed Jun 24, 2022
1 parent bd976bb commit 9341dea
Show file tree
Hide file tree
Showing 128 changed files with 2,190 additions and 1,863 deletions.
778 changes: 0 additions & 778 deletions designs/default-zero-value.md

This file was deleted.

770 changes: 770 additions & 0 deletions designs/defaults-and-model-evolution.md

Large diffs are not rendered by default.

144 changes: 32 additions & 112 deletions designs/enum-shapes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

* **Authors**: Michael Dowling, Jordon Phillips
* **Created**: 2022-02-14
* **Last updated**: 2022-02-14
* **Last updated**: 2022-06-22

## Abstract

Expand Down Expand Up @@ -49,60 +49,31 @@ enum Suit {
Each value listed in the enum is a member that implicitly targets
`smithy.api#Unit`. The string representation of an enum member defaults to the
member name. The string representation can be customized by applying the
`@enumValue` trait.
`@enumValue` trait. We will introduce syntactic sugar to assign the
`@enumValue` trait to enums and intEnums.

```
enum Suit {
@enumValue("diamond")
DIAMOND
@enumValue("club")
CLUB
@enumValue("heart")
HEART
@enumValue("spade")
SPADE
}
```

Enums do not support aliasing; all values MUST be unique.

#### enum default values

The default value of the enum shape is an empty string "", regardless of if
the enum defines a member with a value of "".

```
structure GetFooInput {
@default
suit: Suit
DIAMOND = "diamond"
CLUB = "club"
HEART = "heart"
SPADE = "spade"
}
```

To account for this, enums MAY define a default member by setting the
`@enumDefault` trait on a member:
The above enum definition is exactly equivalent to:

```
enum Suit {
@enumDefault
UNKNOWN
@enumValue("diamond")
DIAMOND
@enumValue("club")
CLUB
@enumValue("heart")
HEART
@enumValue("spade")
SPADE
DIAMOND = "diamond"
CLUB = "club"
HEART = "heart"
SPADE = "spade"
}
```

Enums do not support aliasing; all values MUST be unique.

#### enum is a specialization of string

Enums are considered open, meaning it is a backward compatible change to add
Expand All @@ -127,11 +98,11 @@ Every enum shape MUST define at least one member.

#### enum members always have a value

If an enum member doesn't have an explicit `@enumValue` or `@enumDefault` trait,
an `@enumValue` trait will be automatically added to the member where the trait
value is the member's name. This means that enum members that have neither the
`@enumValue` nor the `@enumDefault` trait are indistinguishable from enum members
that have the `@enumValue` trait explicitly set.
If an enum member doesn't have an explicit `@enumValue` trait, an `@enumValue`
trait will be automatically added to the member where the trait value is the
member's name. This means that enum members that have no `@enumValue` trait
are indistinguishable from enum members that have the `@enumValue` trait
explicitly set.

The following model:

Expand All @@ -148,20 +119,15 @@ Is equivalent to:

```
enum Suit {
@enumValue("DIAMOND")
DIAMOND
@enumValue("CLUB")
CLUB
@enumValue("HEART")
HEART
@enumValue("SPADE")
SPADE
DIAMOND = "DIAMOND"
CLUB = "CLUB"
HEART = "HEART"
SPADE = "SPADE"
}
```

The value of an enum cannot be set to an empty string.

### intEnum shape

An intEnum is used to represent an enumerated set of integer values. The
Expand All @@ -170,61 +136,15 @@ integer value. The following example defines an intEnum shape:

```
intEnum FaceCard {
@enumValue(1)
JACK
@enumValue(2)
QUEEN
@enumValue(3)
KING
@enumValue(4)
ACE
@enumValue(5)
JOKER
}
```

#### intEnum default values

The default value of the intEnum shape is 0, regardless of if the enum defines
a member with a value of 0.

```
structure GetFooInput {
@default
suit: Suit
}
```

intEnums MAY define a member to represent the default value of the shape by
setting the `@enumDefault` trait on a member:

```
intEnum FaceCard {
@enumDefault
UNKNOWN
@enumValue(1)
JACK
@enumValue(2)
QUEEN
@enumValue(3)
KING
@enumValue(4)
ACE
@enumValue(5)
JOKER
JACK = 1
QUEEN = 2
KING = 3
ACE = 4
JOKER = 5
}
```

#### intEnum is a specialization of integer
#### intEnum is a specialization of integer

Like enums, intEnums are considered open, meaning it is a backward compatible
change to add new members. Previously generated clients MUST NOT fail when
Expand Down Expand Up @@ -289,7 +209,7 @@ intEnum and enum shape, client implementations can reliably receive and
round-trip unknown enum values because the unknown value is wire-compatible
with an integer or enum.

### Why do enums contain members but you can’t define the shape they target?
### Why do enums contain members, but you can’t define the shape they target?

Every value of an enum and intEnum is considered a member so that they have a
shape ID and can have traits, but the shape targeted by each enum member is
Expand Down
1 change: 1 addition & 0 deletions docs/smithy/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class SmithyLexer(RegexLexer):
(r'"', String.Double, "string"),
(r"[:,]+", Punctuation),
(r"\s+", Whitespace),
(r"=", Punctuation)
],
"textblock": [
(r"\\(.|$)", String.Escape),
Expand Down
8 changes: 8 additions & 0 deletions docs/source/1.0/guides/converting-to-openapi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,14 @@ Other traits that influence API Gateway
.. seealso:: See :ref:`authorizers`


Amazon API Gateway limitations
==============================

The ``default`` property in OpenAPI is not currently supported by Amazon
API Gateway. The ``default`` property is automatically removed from OpenAPI
models when they are generated for Amazon API Gateway.


-------------------------------
Converting to OpenAPI with code
-------------------------------
Expand Down
23 changes: 7 additions & 16 deletions docs/source/1.0/guides/migrating-idl-1-to-2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ your models.

.. seealso::

:ref:`structure-nullability`
:ref:`structure-optionality`


Replace Primitive prelude shape targets
Expand Down Expand Up @@ -114,8 +114,7 @@ Needs to be updated to:
.. code-block:: smithy
structure User {
@default
name: String
name: String = ""
}
Expand Down Expand Up @@ -160,8 +159,7 @@ Needs to be updated to:
namespace smithy.example
structure OptionalStream {
@default
payload: StreamingBlob
payload: StreamingBlob = ""
}
structure RequiredStream {
Expand Down Expand Up @@ -455,15 +453,8 @@ can be updated to:
namespace smithy.example
enum Suit {
@enumValue("diamond")
DIAMOND
@enumValue("club")
CLUB
@enumValue("heart")
HEART
@enumValue("spade")
SPADE
DIAMOND = "diamond"
CLUB = "club"
HEART = "heart"
SPADE = "spade"
}
6 changes: 3 additions & 3 deletions docs/source/1.0/guides/model-linters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -610,9 +610,9 @@ Rationale
guarantees of the ``@required`` trait. For example, it is considered
backward compatible to remove the ``@required`` trait from a member and
replace it with the ``@default`` trait. However, this isn't possible for
members that target structure or union shapes because they have no zero
value. The risk associated with such members may be unacceptable for some
services.
members that target structure or union shapes because they can have no
default value. The risk associated with such members may be unacceptable
for some services.

Default severity
``DANGER``
Expand Down
35 changes: 9 additions & 26 deletions docs/source/1.0/spec/aws/aws-core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1186,33 +1186,16 @@ setting the ``validationMode`` input property to "ENABLED".
content: Blob
}
@enum([
{
value: "CRC32C",
name: "CRC32C"
},
{
value: "CRC32",
name: "CRC32"
},
{
value: "SHA1",
name: "SHA1"
},
{
value: "SHA256",
name: "SHA256"
}
])
string ChecksumAlgorithm
enum ChecksumAlgorithm {
CRC32C
CRC32
SHA1
SHA256
}
@enum([
{
value: "ENABLED",
name: "ENABLED"
}
])
string ValidationMode
enum ValidationMode {
ENABLED
}
The following trait, which does not define request or response checksum
Expand Down
18 changes: 9 additions & 9 deletions docs/source/1.0/spec/aws/aws-default-zero-value.rst.template
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#. To avoid information disclosure, serializers SHOULD omit the default zero
value of structure members marked with the :ref:`default-trait`.
#. Deserializers MUST tolerate receiving the default zero value of a structure
member marked with the :ref:`default-trait`.
#. Client deserializers MUST fill in a default zero value for structure members
marked with the :ref:`required-trait` that have no serialized value, and the
targeted shape supports zero values. This prevents older clients from
failing to deserialize structures at runtime when the :ref:`required-trait`
is replaced with the :ref:`default-trait`.
#. To avoid information disclosure, serializers SHOULD omit the default
value of structure members that are marked with the :ref:`internal-trait`.
#. Deserializers MUST tolerate receiving the default value of a structure
member.
#. Client deserializers SHOULD fill in a default zero value for structure
members marked with the :ref:`required-trait` that have no serialized
value, and the targeted shape supports zero values. While the server is
in error, it does allow clients to function in the event a service is
miconfigured.
9 changes: 5 additions & 4 deletions docs/source/1.0/spec/aws/customizations/s3-customizations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ Consider the following *abridged* model of S3's ``GetBucketLocation`` operation:

.. code-block:: smithy
$version: "2.0"
use aws.customizations#s3UnwrappedXmlOutput
@http(uri: "/GetBucketLocation", method: "GET")
Expand All @@ -165,10 +167,9 @@ Consider the following *abridged* model of S3's ``GetBucketLocation`` operation:
LocationConstraint: BucketLocationConstraint
}
@enum([
{ value: "us-west-2", name: "us_west_2" }
])
string BucketLocationConstraint
enum BucketLocationConstraint {
us_west_2: "us-west-2"
}
Since this operation is modeled with ``@s3UnwrappedXmlOutput``,
an Amazon S3 client should expect the response from S3 to be unwrapped as shown below:
Expand Down
Loading

0 comments on commit 9341dea

Please sign in to comment.