Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enum shapes #1088

Merged
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
5ef1d93
Add enum shape class and necessary supports
JordonPhillips Feb 9, 2022
52bc929
Add intEnum shape class
JordonPhillips Feb 10, 2022
573ce2b
Add enum and intEnum relationships
JordonPhillips Feb 10, 2022
91878bc
Make enum shape's enum trait synthetic
JordonPhillips Feb 10, 2022
f5c9f8c
Support loading enums in ast
JordonPhillips Feb 10, 2022
a378379
Set enum value trait automatically
JordonPhillips Feb 10, 2022
00aa523
Support loading enums in idl
JordonPhillips Feb 10, 2022
fd10fc5
Add more loader tests
JordonPhillips Feb 10, 2022
3f6beae
Change enum shape category to new enum category
JordonPhillips Feb 10, 2022
6443547
Support model transformer conversion for enums
JordonPhillips Feb 10, 2022
9071fd3
Add EnumValidator
JordonPhillips Feb 14, 2022
4b3ba2a
Add a simple transformer from string to enum
JordonPhillips Feb 14, 2022
d160648
Let model assembler set shapes version
JordonPhillips Feb 14, 2022
031a3d2
Restrict enum shapes to 2.0
JordonPhillips Feb 14, 2022
5edfb17
Make enums be selectable by parent types
JordonPhillips Feb 14, 2022
deccfe7
Add unit test for toSet with subclassing
JordonPhillips Feb 14, 2022
6e4208f
Deprecate enum trait
JordonPhillips Feb 15, 2022
5dca4c7
Disallow applying enum traits to enum shapes
JordonPhillips Feb 15, 2022
72f8f62
Add more enum trait conversion tests
JordonPhillips Feb 15, 2022
ba6eeb0
Support mixins for enum shapes
JordonPhillips Feb 16, 2022
0c3cd5e
Add enum design doc
JordonPhillips Feb 16, 2022
0a314dc
Change shape type categories to match design
JordonPhillips Feb 16, 2022
52bb6ef
Update enum design doc
JordonPhillips Feb 18, 2022
8e59464
Update enum error messages
JordonPhillips Feb 18, 2022
35c1802
Add ability to synthesize enum names
JordonPhillips Feb 22, 2022
d26d24d
Warn when a string can't be converted to enum
JordonPhillips Feb 22, 2022
a374018
Add isShapeType method to ShapeType
JordonPhillips Feb 22, 2022
f8f6efa
Add isShapeTypeSupported to Version
JordonPhillips Feb 22, 2022
d0f6af2
Fix typo: targetsUnion -> targetsUnit
JordonPhillips Feb 22, 2022
dc67daf
Update ModelAssembler docs for unparsed shapes
JordonPhillips Feb 22, 2022
3725c1f
Remove unnecessary members duplication in idl ser
JordonPhillips Feb 22, 2022
879e2bb
Re-privatize enum definitions
JordonPhillips Feb 22, 2022
4d38efd
Add override annotations for NamedMembers methods
JordonPhillips Feb 22, 2022
2dc22b8
Add getEnumValues methods to enum shapes
JordonPhillips Feb 22, 2022
5dbad8b
Add enumDefault trait
JordonPhillips Feb 22, 2022
51a2d95
Set node cache on enum value trait
JordonPhillips Feb 22, 2022
c167f42
Add transformer to downgrade enums
JordonPhillips Feb 22, 2022
e1deac4
Convert enumValue to a document trait
JordonPhillips Feb 24, 2022
46fbfd5
Implement pr feedback
JordonPhillips Feb 24, 2022
a8965b7
Use feature array for changeShapeType
JordonPhillips Feb 25, 2022
d0664f5
Hide away enum conversion guts
JordonPhillips Feb 25, 2022
c7a1aaf
Call out enumValue defaulting in design
JordonPhillips Feb 25, 2022
f27baad
Give specific error for fractions in enumValue
JordonPhillips Feb 25, 2022
bb1d7cf
Move ChangeShapeTypeOption out of ModelTransformer
JordonPhillips Feb 25, 2022
f574098
Clarify enum value defaulting
JordonPhillips Feb 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 272 additions & 0 deletions designs/enum-shapes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
# Enum shapes in Smithy

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

## Abstract

This proposal introduces an `enum` shape and `intEnum` shape to Smithy to
expand the existing `@enum` trait capabilities.

## Motivation

There are two primary motivations:

1. Smithy does not provide the ability to define an enumerated integer, and
many serialization formats serialize enums as integers rather than strings.
2. Enum values are similar to members, but defined in a less flexible way that
requires special casing. Enums in Smithy have enum values that are very
similar to members, but cannot be targeted by traits or filtered like other
members. For example, an `@enum` trait supports the value, name,
documentation, tags, and deprecated properties, of which documentation,
tags, and deprecated provide functionality that is exactly equivalent to
similarly named traits. In order to filter enums out of a model, model
transformations need to write special-cased code to deal with enum traits
rather than relying on the existing logic used to remove member shapes.

## Proposal

Two new simple shapes will be introduced in Smithy 2.0:

* `enum`: An enumerated type that is represented using string values
* `intEnum`: An enumerated type that is represented using integer values.

### Enum shape

The enum shape is used to represent a fixed set of string values. The following
example defines an enum shape:

```
enum Suit {
DIAMOND
CLUB
HEART
SPADE
}
```

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.
JordonPhillips marked this conversation as resolved.
Show resolved Hide resolved

```
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
}
```

To account for this, enums MAY define a default member by setting the
`@enumDefault` trait on a member:

```
enum Suit {
@enumDefault
UNKNOWN

@enumValue("diamond")
DIAMOND

@enumValue("club")
CLUB

@enumValue("heart")
HEART

@enumValue("spade")
SPADE
}
```

#### enum is a specialization of string

Enums are considered open, meaning it is a backward compatible change to add
new members. Previously generated clients MUST NOT fail when they encounter an
unknown enum value. Client implementations MUST provide the capability of
sending and receiving unknown enum values.

To facilitate this behavior, the enum type is a subclass of the string type.
Any selector that accepts a string implicitly accepts an enum. For example, an
enum can be used in an `@httpHeader` or `@httpLabel`.

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

#### enums must define at least one member

Every enum shape MUST define at least one member.

### intEnum shape

An intEnum is used to represent an enumerated set of integer values. The
members of intEnum MUST be marked with the @enumValue trait set to a unique
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
}
```

#### 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
they encounter an unknown intEnum value. Client implementations MUST provide
the capability of sending and receiving unknown intEnum values.

To facilitate this behavior, the intEnum type is a subclass of the integer
type. Any selector that accepts an integer implicitly accepts an intEnum.

Implementation note: In Smithy’s Java implementation, shape visitors will by
default dispatch to the integer shape when an intEnum is encountered. This
both makes adding enums backward compatible, and allows implementations that
do not support enums at all to ignore them.

#### intEnums must define at least one member

Every intEnum shape MUST define at least one member.

### Smithy taxonomy updates

Both enum and intEnum are considered simple shapes though they have members.

The current definition of aggregate shapes is:

> Aggregate types define shapes that are composed of other shapes. Aggregate
> shapes reference other shapes using members.

This will require updating the description of Aggregate Types to become:

> Aggregate types are shapes that can contain more than one value.

The member type in Smithy is currently classified as an aggregate type. Because
members will now be present in simple shapes, we will add a new shape type
called “member” to go alongside simple, aggregate, and service types.

## Smithy 1.0 → 2.0

The `@enum` trait will be deprecated in IDL 2.0, but not removed. This is to
make it easier to migrate to IDL 2.0.

The reference implementation will not automatically upgrade strings bearing
the `@enum` trait into enum shapes since it isn't possible to convert enum
traits that don't set the name property. Instead, a transformer will be added
that upgrades those shapes that can be upgraded.

Additionally, to make this change backwards compatible for exising code
generators, visitors in the reference implementation will call their parent
shape's visitor methods by default. `enum` shapes in the reference
implementation will always contain an `@enum` trait with properties filled
out based on the enum's members and their traits. To prevent this trait
from being serialized, a synthetic variant will be used.

## FAQ

### Why does the enum type dictate its serialization?

RPC systems require and open-world in order for them to evolve without
breaking end users. Previously generated clients need a way to handle
unknown enum values. Discarding unknown values when a client encounters a newly
introduced enum would be a regression in functionality. By making a distinct
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?

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
meaningless. Because of this, members of an enum defined in the IDL implicitly
target Smithy’s unit type, `smithy.api#Unit`.

When defined in the JSON AST, members MUST be defined to target
`smithy.api#Unit`.

### Why does intEnum require an explicit enumValue on every member?

The order and numbers assigned to enum values are extremely important, and
deriving their ordinals implicitly would not allow filtering members based on
the target audience of the model. It is very common in Smithy models to filter
out members from models based on the target audience (for example, internal
clients know about all enum members, private beta customers might know about
specific members, etc).
Loading