A minimal example of NSmithy — a C# implementation of the Smithy IDL.
The service is the Weather example from the Smithy quickstart: a REST API that exposes city resources with paginated listing and per-city weather forecasts.
- Weather.Contracts — Smithy model (
model/weather.smithy). Builds the contract and generates C# types used by both the server and client. - Weather.Server — ASP.NET Core server with a hardcoded in-memory implementation.
- Weather.Client — Console app that exercises the API end-to-end.
Requires the .NET SDK (10.0+), the Smithy CLI (1.73.0+, on PATH) for code generation, and Python (for Smithy doc generation).
Alternatively, use pixi to drop into a dev shell that provides all three (on macOS, Linux and Windows):
pixi shellIf you'd rather not install the Smithy CLI, reference the NSmithy.SmithyCli package instead, which bundles it with its Java runtime — add this to Directory.Packages.props:
<ItemGroup>
<GlobalPackageReference Include="NSmithy.SmithyCli" Version="1.73.0" />
</ItemGroup>just is available in the pixi shell:
just server # start the server
just client # run the client against http://localhost:5000
just client-debug # same, with wire-level request/response loggingOr directly with the .NET CLI:
dotnet run --project Weather.Server
dotnet run --project Weather.ClientThe client connects to http://localhost:5000 by default. Pass a different URL as an argument if needed:
just client http://localhost:8080
# or
dotnet run --project Weather.Client -- http://localhost:8080Pass --debug to log requests, responses, and wire bytes (this is what just client-debug does):
dotnet run --project Weather.Client -- --debugThe server enforces the model at the boundary, so a request that violates it is answered with a structured 4xx rather than reaching the handler. Nothing in the handlers implements these checks — they are generated from the model.
CityId is declared @pattern("^[A-Za-z0-9 ]+$"), so a city ID carrying a character the pattern excludes is rejected with ValidationException, which names the member and the constraint it failed:
curl -i 'http://localhost:5000/cities/SEA%21' # "SEA!"HTTP/1.1 400 Bad Request
X-Amzn-Errortype: ValidationException
{"message":"1 validation error detected. Value at '/cityId' failed to satisfy
constraint: Member must satisfy regular expression pattern: ^[A-Za-z0-9 ]+$",
"fieldList":[{"path":"/cityId","message":"..."}]}
Input that cannot become modeled input at all fails earlier, during deserialization, and comes back as SerializationException — here pageSize is modeled as Integer:
curl -i 'http://localhost:5000/cities?pageSize=abc'HTTP/1.1 400 Bad Request
X-Amzn-Errortype: SerializationException
{"message":"Value 'abc' is not a valid integer."}
An Accept header that excludes the response's media type is answered with 406:
curl -i -H 'Accept: application/xml' http://localhost:5000/cities/SEAHTTP/1.1 406 Not Acceptable
X-Amzn-Errortype: NotAcceptableException
{"message":"Response is 'application/json', which Accept 'application/xml' excludes."}
A request that satisfies the model but names something absent still reaches the handler and returns the error the operation models — a space is inside CityId's pattern, so this is a NoSuchResource, not a validation failure:
curl -i 'http://localhost:5000/cities/%20'HTTP/1.1 400 Bad Request
X-Amzn-Errortype: NoSuchResource
{"resourceType":"City"}
The client exercises both of these — a modeled NoSuchResource and an implicit ValidationException — as typed exceptions rather than bare status codes.
Once the server is running, the following endpoints are also available:
- Scalar API explorer —
http://localhost:5000/openapi - Smithy docs —
http://localhost:5000/docs