Quick start

This document is a tutorial that introduces the Smithy interface definition language (IDL). By reading this tutorial, you will learn:

What is Smithy?

Smithy is an interface definition language and set of tools that allows developers to build clients and servers in multiple languages. Smithy models define a service as a collection of resources, operations, and shapes. A Smithy model enables API providers to generate clients and servers in various programming languages, API documentation, test automation, and example code.

Shapes and traits

Smithy models consist of shapes and traits. Shapes are instances of types. Traits are used to add more information to shapes that might be useful for clients, servers, or documentation.

Smithy supports the following types:

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 An instant in time with no UTC offset or timezone. The serialization of a timestamp is determined by a protocol.
document An untyped JSON-like value.
List Homogeneous collection of values
Set Unordered collection of unique homogeneous values
Map Map data structure that maps string keys to homogeneous values
Structure Fixed set of named heterogeneous members
Union Tagged union data structure that can take on several different, but fixed, types
Service Entry point of an API that aggregates resources and operations together
Operation Represents the input, output and possible errors of an API operation
Resource An entity with an identity, set of operations, and child resources

Weather Service

In order to demonstrate how Smithy models are defined, we will create a weather service.

  1. This service provides weather information for cities.
  2. This service consists of City resources and Forecast resources.
  3. The Weather service has many City resources, and a City resource contains a single Forecast singleton resource.
  4. This service closure contains the following operations: ListCities, GetCity, GetForecast, GetCurrentTime.

Weather is a Service shape that is defined inside of a namespace.

namespace example.weather

/// Provides weather forecasts.
/// Triple slash comments attach documentation to shapes.
service Weather {
    version: "2006-03-01"
}
{
    "smithy": "1.0",
    "shapes": {
        "example.weather#Weather": {
            "type": "service",
            "version": "2006-03-01"
        }
    }
}

What's that syntax?

Smithy models are defined using either the Smithy IDL or the 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.

  • // is used for comments
  • /// is used to add documentation to the following shape.
  • Keywords like service and structure start the definition of a shape.

Defining resources

A resource is contained within a service or another resource. Resources have identifiers, operations, and any number of child resources.

namespace example.weather

/// Provides weather forecasts.
service Weather {
    version: "2006-03-01",
    resources: [City]
}

resource City {
    identifiers: { cityId: CityId },
    read: GetCity,
    list: ListCities,
}

// "pattern" is a trait.
@pattern("^[A-Za-z0-9 ]+$")
string CityId
{
    "smithy": "1.0",
    "shapes": {
        "example.weather#Weather": {
            "type": "service",
            "version": "2006-03-01",
            "resources": [
                {
                    "target": "example.weather#City"
                }
            ]
        },
        "example.weather#City": {
            "type": "resource",
            "identifiers": {
                "cityId": {
                    "target": "example.weather#CityId"
                }
            },
            "read": {
                "target": "example.weather#GetCity"
            },
            "list": {
                "target": "example.weather#ListCities"
            }
        },
        "example.weather#CityId": {
            "type": "string",
            "traits": {
                "smithy.api#pattern": "^[A-Za-z0-9 ]+$"
            }
        }
    }
}

Because the Weather service contains many cities, the City resource defines an identifier. Identifiers are used to refer to a specific resource within a service. The "identifiers" property is a mapping of identifier names to the shape to use for that identifier. If the input structure of an operation uses the same names and targeted shapes as the identifiers property of the resource, the structure is automatically configured to work with the resource so that input members of the operation are used to provide the identity of the resource.

Each City has a single Forecast. This can be defined by adding the Forecast to the resources property of the City.

resource City {
    identifiers: { cityId: CityId },
    read: GetCity,
    list: ListCities,
    resources: [Forecast],
}

resource Forecast {
    identifiers: { cityId: CityId },
    read: GetForecast,
}
{
    "smithy": "1.0",
    "shapes": {
        "example.weather#City": {
            "type": "resource",
            "identifiers": {
                "cityId": {
                    "target": "example.weather#CityId"
                }
            },
            "read": {
                "target": "example.weather#GetCity"
            },
            "list": {
                "target": "example.weather#ListCities"
            },
            "resources": [
                {
                    "target": "example.weather#Forecast"
                }
            ]
        },
        "example.weather#Forecast": {
            "type": "resource",
            "identifiers": {
                "cityId": {
                    "target": "example.weather#CityId"
                }
            },
            "read": {
                "target": "example.weather#GetForecast"
            }
        }
    }
}

Child resources must define the exact same identifiers property of their parent, but they are allowed to add any number of additional identifiers if needed. Because there is only one forecast per city, no additional identifiers were added to the identifiers property that isn't present on the City resource.

Review

  1. The resources property binds resources to service and resource shapes.
  2. Resources can define identifiers.
  3. Child resources must define the same identifiers as their parents, and they can also define additional identifiers.

Defining operations

The create, read, update, delete, and list properties of a resource are used to define the lifecycle operations of a resource. Lifecycle operations are the canonical methods used to read and transition the state of a resource using well-defined semantics. Defining lifecycle operations helps automated tooling reason about your API.

Let's define the operation used to "read" a City.

@readonly
operation GetCity {
    input: GetCityInput,
    output: GetCityOutput,
    errors: [NoSuchResource]
}

structure GetCityInput {
    // "cityId" provides the identifier for the resource and
    // has to be marked as required.
    @required
    cityId: CityId
}

structure GetCityOutput {
    // "required" is used on output to indicate if the service
    // will always provide a value for the member.
    @required
    name: String,

    @required
    coordinates: CityCoordinates,
}

structure CityCoordinates {
    @required
    latitude: Float,

    @required
    longitude: Float,
}

// "error" is a trait that is used to specialize
// a structure as an error.
@error("client")
structure NoSuchResource {
    @required
    resourceType: String
}
{
    "smithy": "1.0",
    "shapes": {
        "example.weather#GetCity": {
            "type": "operation",
            "input": {
                "target": "example.weather#GetCityInput"
            },
            "output": {
                "target": "example.weather#GetCityOutput"
            },
            "errors": [
                {
                    "target": "example.weather#NoSuchResource"
                }
            ]
        },
        "example.weather#GetCityInput": {
            "type": "structure",
            "members": {
                "cityId": {
                    "target": "example.weather#CityId",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            }
        },
        "example.weather#GetCityOutput": {
            "type": "structure",
            "members": {
                "name": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#required": true
                    }
                },
                "coordinates": {
                    "target": "example.weather#CityCoordinates",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            }
        },
        "example.weather#CityCoordinates": {
            "type": "structure",
            "members": {
                "latitude": {
                    "target": "smithy.api#Float",
                    "traits": {
                        "smithy.api#required": true
                    }
                },
                "longitude": {
                    "target": "smithy.api#Float",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            }
        },
        "example.weather#NoSuchResource": {
            "type": "structure",
            "members": {
                "resourceType": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            },
            "traits": {
                "smithy.api#error": "client"
            }
        }
    }
}

Review

  1. Operations accept and return structured messages.
  2. Operations are bound to service shapes and resource shapes.
  3. Operations marked as readonly trait indicate the operation has no side effects.
  4. Operations should define the errors it can return.

Listing resources

There are many City resources contained within the Weather service. The list lifecycle operation can be added to the City resource to list all of the cities in the service. The list operation is a collection operation, and as such, MUST NOT bind the identifier of a City to its input structure; we are listing cities, so there's no way we could provide a City identifier.

/// Provides weather forecasts.
@paginated(inputToken: "nextToken", outputToken: "nextToken",
           pageSize: "pageSize")
service Weather {
    version: "2006-03-01",
    resources: [City]
}

// The paginated trait indicates that the operation may
// return truncated results. Applying this trait to the service
// sets default pagination configuration settings on each operation.
@paginated(items: "items")
@readonly
operation ListCities {
    input: ListCitiesInput,
    output: ListCitiesOutput
}

structure ListCitiesInput {
    nextToken: String,
    pageSize: Integer
}

structure ListCitiesOutput {
    nextToken: String,

    @required
    items: CitySummaries,
}

// CitySummaries is a list of CitySummary structures.
list CitySummaries {
    member: CitySummary
}

// CitySummary contains a reference to a City.
@references([{resource: City}])
structure CitySummary {
    @required
    cityId: CityId,

    @required
    name: String,
}
{
    "smithy": "1.0",
    "shapes": {
        "example.weather#Weather": {
            "type": "service",
            "version": "2006-03-01",
            "resources": [
                {
                    "target": "example.weather#City"
                }
            ],
            "traits": {
                "smithy.api#paginated": {
                    "inputToken": "nextToken",
                    "outputToken": "nextToken",
                    "pageSize": "pageSize"
                }
            }
        },
        "example.weather#ListCities": {
            "type": "operation",
            "input": {
                "target": "example.weather#ListCitiesInput"
            },
            "output": {
                "target": "example.weather#ListCitiesOutput"
            },
            "traits": {
                "smithy.api#readonly": true,
                "smithy.api#paginated": {
                    "items": "items"
                }
            }
        },
        "example.weather#ListCitiesInput": {
            "type": "structure",
            "members": {
                "nextToken": {
                    "target": "smithy.api#String"
                },
                "pageSize": {
                    "target": "smithy.api#Integer"
                }
            }
        },
        "example.weather#ListCitiesOutput": {
            "type": "structure",
            "members": {
                "nextToken": {
                    "target": "smithy.api#String"
                },
                "items": {
                    "target": "example.weather#CitySummaries",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            }
        },
        "example.weather#CitySummaries": {
            "type": "list",
            "member": {
                "target": "example.weather#CitySummary"
            }
        },
        "example.weather#CitySummary": {
            "type": "structure",
            "members": {
                "cityId": {
                    "target": "example.weather#CityId",
                    "traits": {
                        "smithy.api#required": true
                    }
                },
                "name": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            }
        }
    }
}

The ListCities operation is paginated, meaning the results of invoking the operation can be truncated, requiring subsequent 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 reference to a City resource. This gives tooling a better understanding of the relationships in your service.

The above example refers to prelude shapes like String that are automatically available in all Smithy models.

Review

  1. The list lifecycle operation is used to list resources.
  2. list operations should be paginated trait.
  3. The references trait links a structure to a resource.
  4. Prelude shapes can help DRY up models.

Non-Lifecycle Operations

Smithy supports operations that don't fit into the typical create, read, update, delete, and list lifecycles. Operations can be added to any resource or service shape with no special lifecycle designation using the operations property. The following operation gets the current time from the Weather service.

/// Provides weather forecasts.
@paginated(inputToken: "nextToken", outputToken: "nextToken",
           pageSize: "pageSize")
service Weather {
    version: "2006-03-01",
    resources: [City],
    operations: [GetCurrentTime]
}

@readonly
operation GetCurrentTime {
    output: GetCurrentTimeOutput
}

structure GetCurrentTimeOutput {
    @required
    time: Timestamp
}
{
    "smithy": "1.0",
    "shapes": {
        "example.weather#Weather": {
            "type": "service",
            "version": "2006-03-01",
            "resources": [
                {
                    "target": "example.weather#City"
                }
            ],
            "operations": [
                {
                    "target": "example.weather#GetCurrentTime"
                }
            ]
        },
        "example.weather#GetCurrentTime": {
            "type": "operation",
            "output": {
                "target": "example.weather#GetCurrentTimeOutput"
            },
            "traits": {
                "smithy.api#readonly": true
            }
        },
        "example.weather#GetCurrentTimeOutput": {
            "type": "structure",
            "members": {
                "time": {
                    "target": "smithy.api#Timestamp",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            }
        }
    }
}

Next steps

That's it! We just created a simple, read-only, Weather service.

  1. Try adding a "create" lifecycle operation to City.
  2. Try adding a "delete" lifecycle operation to City.
  3. Try adding HTTP binding traits to the API.

There's plenty more to explore in Smithy. The Smithy specification can teach you everything you need to know about Smithy.

Complete example

namespace example.weather

/// Provides weather forecasts.
@paginated(inputToken: "nextToken", outputToken: "nextToken",
           pageSize: "pageSize")
service Weather {
    version: "2006-03-01",
    resources: [City],
    operations: [GetCurrentTime]
}

resource City {
    identifiers: { cityId: CityId },
    read: GetCity,
    list: ListCities,
    resources: [Forecast],
}

resource Forecast {
    identifiers: { cityId: CityId },
    read: GetForecast,
}

// "pattern" is a trait.
@pattern("^[A-Za-z0-9 ]+$")
string CityId

@readonly
operation GetCity {
    input: GetCityInput,
    output: GetCityOutput,
    errors: [NoSuchResource]
}

structure GetCityInput {
    // "cityId" provides the identifier for the resource and
    // has to be marked as required.
    @required
    cityId: CityId
}

structure GetCityOutput {
    // "required" is used on output to indicate if the service
    // will always provide a value for the member.
    @required
    name: String,

    @required
    coordinates: CityCoordinates,
}

// This structure is nested within GetCityOutput.
structure CityCoordinates {
    @required
    latitude: Float,

    @required
    longitude: Float,
}

// "error" is a trait that is used to specialize
// a structure as an error.
@error("client")
structure NoSuchResource {
    @required
    resourceType: String
}

// The paginated trait indicates that the operation may
// return truncated results.
@readonly
@paginated(items: "items")
operation ListCities {
    input: ListCitiesInput,
    output: ListCitiesOutput
}

structure ListCitiesInput {
    nextToken: String,
    pageSize: Integer
}

structure ListCitiesOutput {
    nextToken: String,

    @required
    items: CitySummaries,
}

// CitySummaries is a list of CitySummary structures.
list CitySummaries {
    member: CitySummary
}

// CitySummary contains a reference to a City.
@references([{resource: City}])
structure CitySummary {
    @required
    cityId: CityId,

    @required
    name: String,
}

@readonly
operation GetCurrentTime {
    output: GetCurrentTimeOutput
}

structure GetCurrentTimeOutput {
    @required
    time: Timestamp
}

@readonly
operation GetForecast {
    input: GetForecastInput,
    output: GetForecastOutput
}

// "cityId" provides the only identifier for the resource since
// a Forecast doesn't have its own.
structure GetForecastInput {
    @required
    cityId: CityId,
}

structure GetForecastOutput {
    chanceOfRain: Float
}
{
    "smithy": "1.0",
    "shapes": {
        "example.weather#Weather": {
            "type": "service",
            "version": "2006-03-01",
            "operations": [
                {
                    "target": "example.weather#GetCurrentTime"
                }
            ],
            "resources": [
                {
                    "target": "example.weather#City"
                }
            ],
            "traits": {
                "smithy.api#paginated": {
                    "inputToken": "nextToken",
                    "outputToken": "nextToken",
                    "pageSize": "pageSize"
                }
            }
        },
        "example.weather#City": {
            "type": "resource",
            "identifiers": {
                "cityId": {
                    "target": "example.weather#CityId"
                }
            },
            "read": {
                "target": "example.weather#GetCity"
            },
            "list": {
                "target": "example.weather#ListCities"
            },
            "resources": [
                {
                    "target": "example.weather#Forecast"
                }
            ]
        },
        "example.weather#CityCoordinates": {
            "type": "structure",
            "members": {
                "latitude": {
                    "target": "smithy.api#Float",
                    "traits": {
                        "smithy.api#required": true
                    }
                },
                "longitude": {
                    "target": "smithy.api#Float",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            }
        },
        "example.weather#CityId": {
            "type": "string",
            "traits": {
                "smithy.api#pattern": "^[A-Za-z0-9 ]+$"
            }
        },
        "example.weather#CitySummaries": {
            "type": "list",
            "member": {
                "target": "example.weather#CitySummary"
            }
        },
        "example.weather#CitySummary": {
            "type": "structure",
            "members": {
                "cityId": {
                    "target": "example.weather#CityId",
                    "traits": {
                        "smithy.api#required": true
                    }
                },
                "name": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            },
            "traits": {
                "smithy.api#references": [
                    {
                        "resource": "City"
                    }
                ]
            }
        },
        "example.weather#Forecast": {
            "type": "resource",
            "identifiers": {
                "cityId": {
                    "target": "example.weather#CityId"
                }
            },
            "read": {
                "target": "example.weather#GetForecast"
            }
        },
        "example.weather#GetCity": {
            "type": "operation",
            "input": {
                "target": "example.weather#GetCityInput"
            },
            "output": {
                "target": "example.weather#GetCityOutput"
            },
            "errors": [
                {
                    "target": "example.weather#NoSuchResource"
                }
            ],
            "traits": {
                "smithy.api#readonly": true
            }
        },
        "example.weather#GetCityInput": {
            "type": "structure",
            "members": {
                "cityId": {
                    "target": "example.weather#CityId",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            }
        },
        "example.weather#GetCityOutput": {
            "type": "structure",
            "members": {
                "coordinates": {
                    "target": "example.weather#CityCoordinates",
                    "traits": {
                        "smithy.api#required": true
                    }
                },
                "name": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            }
        },
        "example.weather#GetCurrentTime": {
            "type": "operation",
            "output": {
                "target": "example.weather#GetCurrentTimeOutput"
            },
            "traits": {
                "smithy.api#readonly": true
            }
        },
        "example.weather#GetCurrentTimeOutput": {
            "type": "structure",
            "members": {
                "time": {
                    "target": "smithy.api#Timestamp",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            }
        },
        "example.weather#GetForecast": {
            "type": "operation",
            "input": {
                "target": "example.weather#GetForecastInput"
            },
            "output": {
                "target": "example.weather#GetForecastOutput"
            },
            "traits": {
                "smithy.api#readonly": true
            }
        },
        "example.weather#GetForecastInput": {
            "type": "structure",
            "members": {
                "cityId": {
                    "target": "example.weather#CityId",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            }
        },
        "example.weather#GetForecastOutput": {
            "type": "structure",
            "members": {
                "chanceOfRain": {
                    "target": "smithy.api#Float"
                }
            }
        },
        "example.weather#ListCities": {
            "type": "operation",
            "input": {
                "target": "example.weather#ListCitiesInput"
            },
            "output": {
                "target": "example.weather#ListCitiesOutput"
            },
            "traits": {
                "smithy.api#paginated": {
                    "items": "items"
                },
                "smithy.api#readonly": true
            }
        },
        "example.weather#ListCitiesInput": {
            "type": "structure",
            "members": {
                "nextToken": {
                    "target": "smithy.api#String"
                },
                "pageSize": {
                    "target": "smithy.api#Integer"
                }
            }
        },
        "example.weather#ListCitiesOutput": {
            "type": "structure",
            "members": {
                "items": {
                    "target": "example.weather#CitySummaries",
                    "traits": {
                        "smithy.api#required": true
                    }
                },
                "nextToken": {
                    "target": "smithy.api#String"
                }
            }
        },
        "example.weather#NoSuchResource": {
            "type": "structure",
            "members": {
                "resourceType": {
                    "target": "smithy.api#String",
                    "traits": {
                        "smithy.api#required": true
                    }
                }
            },
            "traits": {
                "smithy.api#error": "client"
            }
        }
    }
}
Specifications →