Skip to content

Modeling contracts

A Smithy contract describes what a service does: its operations, the data they exchange, and the errors they can return. How requests travel over the wire is a separate decision, added to the model as protocol bindings. NSmithy generates the same C# types and handler interfaces whichever bindings you choose.

A shape is a named definition: a data type, an operation, a resource, or a service. A trait annotates a shape or member with additional meaning, such as a constraint or a protocol binding.

This model, adapted from the Smithy quickstart, lets callers look up a city by identifier:

model/weather.smithy
$version: "2"
namespace example.weather
service Weather {
version: "2006-03-01"
resources: [City]
}
resource City {
identifiers: { cityId: CityId }
properties: { name: String, coordinates: CityCoordinates }
read: GetCity
}
@pattern("^[A-Za-z0-9 ]+$")
string CityId
structure CityCoordinates {
@required
latitude: Float
@required
longitude: Float
}
@readonly
operation GetCity {
input := for City {
@required
$cityId
}
output := for City {
@required
$name
@required
$coordinates
}
errors: [NoSuchResource]
}
@error("client")
structure NoSuchResource {
@required
resourceType: String
}

The service lists its resources, the resource ties a city’s identifier and properties to the operation that reads it, and the operation defines what crosses the service boundary. := declares the inline structures GetCityInput and GetCityOutput; for City lets $cityId, $name, and $coordinates reuse the resource’s member definitions through target elision.

Traits carry the rest of the meaning. @required and @pattern constrain values, @readonly states that the operation has no side effects, and @error("client") classifies the failure. None of them says anything about HTTP: GetCity has no method or route, and NoSuchResource has no status code.

A protocol defines encoding, dispatch, and error representation. A service selects one with a trait, and some protocols need further bindings on operations and members. apply attaches a trait to an existing shape, so the bindings can live in a second .smithy file in the same namespace:

model/weather-bindings.smithy
$version: "2"
namespace example.weather
apply Weather @aws.protocols#restJson1
apply GetCity @http(method: "GET", uri: "/cities/{cityId}", code: 200)
apply GetCityInput$cityId @httpLabel
apply NoSuchResource @httpError(404)

GetCity is now GET /cities/{cityId} with cityId taken from the path, and NoSuchResource maps to 404. The same traits can be written inline above each shape instead; a separate file keeps the contract readable and the bindings reviewable together. GetCityInput$cityId names a member of a shape and is unrelated to the $cityId elision inside for City.

RPC v2 CBOR needs only the service trait, because it dispatches on operation names rather than routes:

apply Weather @smithy.protocols#rpcv2Cbor

A service may declare both, and NSmithy serves them from one implementation; see Hosting multiple protocols. The Protocols overview compares the options. Continue with How NSmithy works to see how the model becomes C# and what runs it.