5. Mixins#

A mixin is a shape that has the mixin trait. Adding a mixin to a shape causes the members and traits of the mixin shape to be copied into the local shape.

$version: "2"
namespace smithy.example

@mixin
structure UserIdentifiersMixin {
    id: String
}

structure UserDetails with [UserIdentifiersMixin] {
    alias: String
}

Multiple mixins can be applied:

@mixin
structure UserIdentifiersMixin {
    id: String
}

@mixin
structure AccessDetailsMixin {
    firstAccess: Timestamp
    lastAccess: Timestamp
}

structure UserDetails with [
    UserIdentifiersMixin
    AccessDetailsMixin
] {
    alias: String
}

Mixins can be composed of other mixins:

@mixin
structure MixinA {
    a: String
}

@mixin
structure MixinB with [MixinA] {
    b: String
}

structure C with [MixinB] {
    c: String
}

When a member is copied from a mixin into a target shape, the shape ID of the copied member takes on the containing shape ID of the target shape. This ensures that members defined via mixins are treated the same way as members defined directly in a shape, and it allows members of a shape to be backward compatibly refactored and moved into a mixin or for a shape to remove a mixin and replace it with members defined directly in the shape.

The above C structure is equivalent to the following flattened structure without mixins:

structure C {
    a: String
    b: String
    c: String
}

Mixins be any shape type, but they MUST NOT be applied to a shape of a different type. For example, a string mixin can be applied to a string shape, but not to a blob shape.

@mixin
@pattern("[a-zA-Z0-1]*")
string AlphaNumericMixin

@length(min: 8, max: 32)
string Username with [AlphaNumericMixin]

5.1. Traits and mixins#

Shapes that use mixins inherit the traits applied to their mixins, except for the mixin trait and mixin local traits. Traits applied directly to a shape take precedence over traits applied to its mixins.

For example, the definition of UserSummary in the following model:

/// Generic mixin documentation.
@tags(["a"])
@mixin
structure UserInfoMixin {
    userId: String
}

structure UserSummary with [UserInfoMixin] {}

Is equivalent to the following flattened structure because it inherits the traits of UserInfo:

/// Generic mixin documentation.
@tags(["a"])
structure UserSummary {
    userId: String
}

The definition of UserSummary in the following model:

/// Generic mixin documentation.
@tags(["a"])
@mixin
structure UserInfoMixin {
    userId: String
}

/// Specific documentation
@tags(["replaced-tags"])
structure UserSummary with [UserInfoMixin] {}

Is equivalent to the following flattened structure because it inherits the traits of UserInfo and traits applied to UserSummary take precedence over traits it inherits:

/// Specific documentation
@tags(["replaced-tags"])
structure UserSummary {
    userId: String
}

The order in which mixins are applied to a shape controls the inheritance precedence of traits. For each mixin applied to a shape, traits applied directly to the mixin override traits applied to any of its mixins. Traits applied to mixins that come later in the list of mixins applied to a shape take precedence over traits applied to mixins that come earlier in the list of mixins. For example, the definition of StructD in the following model:

/// A
@foo(1)
@oneTrait
@mixin
structure StructA {}

/// B
@foo(2)
@twoTrait
@mixin
structure StructB {}

/// C
@threeTrait
@mixin
structure StructC with [StructA, StructB] {}

/// D
@fourTrait
structure StructD with [StructC] {}

Is equivalent to the following flattened structure:

// (1)
/// D
@fourTrait    // (2)
@threeTrait   // (3)
@foo(2)       // (4)
@twoTrait     // (5)
@oneTrait     // (6)
structure StructD {}
  1. The documentation trait applied to StructD takes precedence over any inherited traits.
  2. fourTrait is applied directly to StructD.
  3. threeTrait is applied to StructC, StructC is a mixin of StructD, and StructD inherits the resolved traits of each applied mixin.
  4. Because the StructB mixin applied to StructC comes after the StructA mixin in the list of mixins applied to StructC, foo(2) takes precedence over foo(1).
  5. StructC inherits the resolved traits of StructB.
  6. StructC inherits the resolved traits of StructA.

5.1.1. Mixin local traits#

Sometimes it's necessary to apply traits to a mixin that are not copied onto shapes that use the mixin. For example, if a mixin is an implementation detail of a model, then it is recommended to apply the private trait to the mixin so that shapes outside of the namespace the mixin is defined within cannot refer to the mixin. However, every shape that uses the mixin doesn't necessarily need to be marked as private. The localTraits property of the mixin trait can be used to ensure that a list of traits applied to the mixin are not copied onto shapes that use the mixin (note that this has no effect on the traits applied to members contained within a mixin).

Consider the following model:

$version: "2"
namespace smithy.example

@private
@mixin(localTraits: [private])
structure PrivateMixin {
    foo: String
}

structure PublicShape with [PrivateMixin] {}

PublicShape is equivalent to the following flattened structure:

structure PublicShape {
    foo: String
}

The PrivateMixin shape can only be referenced from the smithy.example namespace. Because the private trait is present in the localTraits property of the mixin trait, PublicShape is not marked with the private trait and can be referred to outside of smithy.example.

5.1.2. Adding and replacing traits on copied members#

The members and traits applied to members of a mixin are copied onto the target shape. It is sometimes necessary to provide a more specific trait value for a copied member or to add traits only to a specific copy of a member. Traits can be added on to these members like any other member. Additionally, traits can be applied to these members in the JSON AST using the apply type and in the Smithy IDL using apply statements.

Note

Traits applied to shapes supersede any traits inherited from mixins.

For example:

$version: "2"
namespace smithy.example

@mixin
structure MyMixin {
    /// Generic docs
    mixinMember: String
}

structure MyStruct with [MyMixin] {}
apply MyStruct$mixinMember @documentation("Specific docs")

Alternatively, the member can be redefined if it targets the same shape:

$version: "2"
namespace smithy.example

@mixin
structure MyMixin {
    /// Generic docs
    mixinMember: String
}

structure MyStruct with [MyMixin] {
    /// Specific docs
    mixinMember: String
}

5.2. Mixins are an implementation detail of the model#

Mixins are an implementation detail of models and are only intended to reduce duplication in Smithy shape definitions. Mixins do not provide any kind of runtime polymorphism for types generated from Smithy models. Smithy model transformations like code generation or converting to other model formats like OpenAPI SHOULD completely elide mixins by flattening the model.

It is a backward compatible change to remove a mixin from a shape as long as equivalent traits and members of the mixin are applied directly to the shape. Such a change should have no impact on generated artifacts like code or OpenAPI models.

5.3. Mixins cannot be referenced other than as mixins to other shapes#

To ensure that mixins are not code generated, mixins MUST NOT be referenced from any other shapes except to mix them into other shapes. Mixins MUST NOT be used as operation input, output, or errors, and they MUST NOT be targeted by members.

The following model is invalid because a structure member targets a mixin:

@mixin
structure GreetingMixin {
    greeting: String
}

structure InvalidStructure {
    notValid: GreetingMixin // <- this is invalid
}

The following model is invalid because an operation attempts to use a mixin as input:

@mixin
structure InputMixin {}

operation InvalidOperation {
    input: InputMixin // <- this is invalid
}

5.4. Mixins MUST NOT introduce cycles#

Mixins MUST NOT introduce circular references. The following model is invalid:

@mixin
structure CycleA with [CycleB] {}

@mixin
structure CycleB with [CycleA] {}

5.5. Mixin members MUST NOT conflict#

The list of mixins applied to a shape MUST NOT attempt to define members that use the same member name with different targets. The following model is invalid:

@mixin
structure A1 {
    a: String
}

@mixin
structure A2 {
    a: Integer
}

structure Invalid with [A1, A2] {}

The following model is also invalid, but not specifically because of mixins. This model is invalid because the member name a and A case insensitively conflict.

@mixin
structure A1 {
    a: String
}

@mixin
structure A2 {
    A: Integer
}

structure Invalid with [A1, A2] {}

Members that are mixed into shapes MAY be redefined if and only if each redefined member targets the same shape. Traits applied to redefined members supersede any traits inherited from mixins.

@mixin
structure A1 {
    @private
    a: String
}

@mixin
structure A2 {
    @required
    a: String
}

structure Valid with [A1, A2] {}

5.6. Member ordering#

The order of structure and union members is important for languages like C that require a stable ABI. Mixins provide a deterministic member ordering. Members inherited from mixins come before members defined directly in the shape.

Members are ordered in a kind of depth-first, preorder traversal of mixins that are applied to a structure or union. To resolve the member order of a shape, iterate over each mixin applied to the shape in the order in which they are applied, from left to right. For each mixin, iterate over the mixins applied to the mixin in the order in which mixins are applied. When the evaluated shape has no mixins, the members of that shape are added to the resolved list of ordered members. After evaluating all the mixins of a shape, the members of the shape are added onto the resolved list of ordered members. This process continues until all mixins and the members of the starting shape are added to the ordered list.

Given the following model:

@mixin
structure FilteredByNameMixin {
    nameFilter: String
}

@mixin
structure PaginatedInputMixin {
    nextToken: String
    pageSize: Integer
}

structure ListSomethingInput with [
    PaginatedInputMixin
    FilteredByNameMixin
] {
    sizeFilter: Integer
}

The members are ordered as follows:

  1. nextToken
  2. pageSize
  3. nameFilter
  4. sizeFilter

5.7. Mixins on shapes with non-member properties#

Some shapes don't have members, but do have other properties. Adding a mixin to such a shape merges the properties of each mixin into the local shape. Only certain properties may be defined in the mixin shapes. See the sections below for which properties are permitted for each shape type.

Scalar properties defined in the local shape are kept, and non-scalar properties are merged. When merging map properties, the values for local keys are kept. The ordering of merged lists / sets follows the same ordering as members.

5.7.1. Service mixins#

Service shapes with the mixin trait may define any property. For example, in the following model:

operation OperationA {}

@mixin
service A {
    version: "A"
    operations: [OperationA]
}

operation OperationB {}

@mixin
service B with [A] {
    version: "B"
    rename: {
        "smithy.example#OperationA": "OperA"
        "smithy.example#OperationB": "OperB"
    }
    operations: [OperationB]
}

operation OperationC {}

service C with [B] {
    version: "C"
    rename: {
        "smithy.example#OperationA": "OpA"
        "smithy.example#OperationC": "OpC"
    }
    operations: [OperationC]
}

The flattened equivalent of C with no mixins is:

operation OperationA {}

operation OperationB {}

operation OperationC {}

service C {
    version: "C"
    rename: {
        "smithy.example#OperationA": "OpA"
        "smithy.example#OperationB": "OperB"
        "smithy.example#OperationC": "OpC"
    }
    operations: [OperationA, OperationB, OperationC]
}

5.7.2. Resource mixins#

Resource shapes with the mixin trait MAY NOT define any properties. This is because every property of a resource shape is intrinsically tied to its set of identifiers. Changing these identifiers would invalidate every other property of a given resource. For example:

@mixin
@internal
resource MixinResource {}

resource MixedResource with [MixinResource] {}

5.7.3. Operation mixins#

Operation shapes with the mixin trait MAY NOT define an input or output shape other than the Unit type. This is because allowing input and output shapes to be shared goes against the goal of the input trait and output trait.

Operation shapes with the mixin trait MAY define errors.

@mixin
operation ValidatedOperation {
    errors: [ValidationError]
}

@error("client")
structure ValidationError {}

operation GetUsername with [ValidatedOperation] {
    input := {
        id: String
    }
    output := {
        name: String
    }
    error: [NotFoundError]
}

@error("client")
structure NotFoundError {}