logo

smithy4rs

Smithy code generators for Rust

Build Status License crates.io dependencies

smithy4rs provides Smithy code generators for clients, server, and shapes for the Rust programming language.

Generated code supports protocol-agnostic clients, servers, and event systems via schema-guided (de)serialization where schema metadata is made available at runtime.

Info

smithy4rs is an unofficial, community-supported Smithy code generator for Rust.

If you are looking for the official code generators used to create the the AWS Rust SDK, head over to smithy-rs.

Core components

smithy4rs contains three core types of components:

  1. smithy-build plugins - used to integrate with the smithy toolchain and generate code from a Smithy model.
  2. Core libraries - provide the core functionality for generated Rust code.
  3. Protocol implementations - libraries that implement (de)serialization and binding logic that can be used to interface between clients and servers.

Early Access

All interfaces of code generators, core libraries, and protocol implementations are unstable and may be subject to change without notice or guaranteed of backwards-compatibility.

Getting Started

For a guided introduction to this project, see the Quick Start guide.

For a more in-depth exploration, check out our Tutorials.


FAQ

What is different between smithy4rs and the official code generators?

The official AWS rust code generators create "model ignorant" code. No schema metadata is available at runtime and (de)serialization code is generated on a per-protocol basis. This approach can lead to fast (de)serialization at the expense of extensibility/flexibility.

In contrast, smithy4rs generates "schema-aware" code. Schema metadata is made available to (de)serializers and schemas can even be created and used dynamically at runtime. The flexibility of this approach allows smithy4rs to support a number of features that the AWS Rust code generators do not:

  1. serde compatibility - smithy4rs allows users to optionally (de)serialize shapes using the rust serde ecosystem. See serde-compatibility for more info on how to enable this feature.
  2. Type codegen - smithy4rs allows users to generate standalone types without need for a client or service. This makes the development of more flexible middleware possible for clients and servers and even opens up the possibility of generating event types.
  3. Protocol-agnosticism - The shapes, clients, and servers generated by smithy4rs are fully protocol-agnostic. In contrast, smithy-rs generates protocol-specific (de)serialization code.
  4. Dynamic schemas - smithy4rs is designed to allow users to dynamically load and use schemas for (de)serialization. This can be used to support fully dynamic clients and server-mocks.
  5. "Any"-ish Document support - smithy-java introduced a concept of documents as an Any type for the Smithy data model. smithy4rs provides first class support for this new Document model.

What does it mean to be "protocol agnostic"?

A "protocol" is composed of both the transport (i.e. HTTP or MQTT) and serialization format (i.e. JSON or XML) used to communicate data between clients and servers.

Data representations in the Smithy IDL are not tied to any specific wire format or transport. In contrast to IDL's like protobuf (gRPC), this allows Smithy servers and clients to support any protocol they would like, even swapping out client protocols at runtime.

This ability to be "agnostic" to the final wire format and transport is what is meant when we say Smithy is "protocol-agnostic".

Quick Start

This guide introduces [smithy4rs] with a simple working example of generating (de)serializable types .

New to Smithy?

If you are new to Smithy, we recommend going through the Smithy Quickstart guide before using smithy4rs. The guide will walk you through the basics of creating a simple Smithy model.

Prerequisites

Setup

Fast-path: Templates

TODO: Update once a template is available via cargo and smithy-init templating

Create a new rust project

First, create a new Rust project by running cargo init in a new directory. This will set up the basic boilerplate for our crate.

Next, add smithy-cargo as a build dependency:

cargo add --build smithy-cargo

This tool will integrate the Smithy build tooling with Cargo.

Also add smithy4rs-core as a dependency:

cargo add smithy4rs-core

This package defines the Smithy data model in rust and adds all the core functionality (such as serialization) for generate shapes.

Set up Smithy build tooling

We will now set up smithy-cargo to execute the Smithy CLI as part of the cargo build process.

First, create a build.rs file at the root of your project and add the following build script:

use smithy_cargo::SmithyBuild;

fn main() {
  /// Executes the `smithy` CLI `build` command from cargo
  /// and configures some environment variables to point to the 
  /// generated output folder.
  SmithyBuild::new().execute().expect("Smithy Build failed");
}

Then, add a smithy-build.json file to the root of your project to configure the Smithy build:

{
  "version": "1.0",
  "maven": {
    "dependencies": [
      "dev.hmellema.smithy4rs:type-codegen:1.0.0"
    ]
  },
  "plugins": {
    "rust-types": {
      "TODO": "ADD REAL CONFIG"
    }
  }
}

Now, we are ready to create our model!

Event Model

For this quickstart we are going to generate a few event shapes.

TODO: Come up with a bit more interesting framing

namespace com.quickstart.example

/// Doc comment
structure EventA {
    int: Integer
}

Using the generated shapes

To use our generated shapes, simply create a module add import them using the generated_shapes! macro.

#![allow(unused)]
fn main() {
mod shapes {
    use smithy4rs_core::generated_shapes;

    generated_shapes![];
}
}

We can now use our generated shapes elsewhere in our rust code:

fn main() {
  // Create a new, validated event instance
  let event = EventA::builder()
          .int(42)
          .build()
          .expect("Should Build");
  // pretty-print the event
  println!("{:#?}", event)
}

Tutorials

Under construction

This page is under construction.

Type Codegen User Guide

Under construction

This page is under construction.

Client User Guide

This guide provides a detailed walkthrough of how to use and configure smithy4rs code generators to generate Rust clients (SDKs) from a Smithy model.

Warning

SDK code generation is not supported at this time, but is planned.

To provide input on the development of SDK codegen please create an issue or discussion on the main repo

Server User Guide

This guide provides a detailed walkthrough of how to use and configure smithy4rs code generators to generate Rust server stubs from a Smithy model.

Warning

Server-stub code generation is not supported at this time.

To provide input on the development of server codegen please create an issue or discussion on the main repo

Schemas

This page provides an overview of the Smithy data model and Schema implementation in smithy4rs.

Under construction

This page is under construction.

Documents

This page provides an overview of the Document types in smithy4rs.

Under construction

This page is under construction.

Serde Compatibility

This page walks through how to enable serde (de)serialization for shapes generated by smithy4rs.

Under construction

This page is under construction.

Validation

This page provides an overview of the Validation in smithy4rs.

Under construction

This page is under construction.

Fuzz testing

Fuzz testing provides pseudo-random data as the input to code, running until a fatal error is encountered or a time or iteration limit is reached. Fuzz testing can detect potential security and/or stability issues in your Rust code.

Shapes generated by smithy4rs support fuzzing with the cargo-fuzz rust fuzzing framework.

Structure-Aware fuzzing with smithy4rs

To use generated shapes for structure-aware fuzzing, enable the arbitrary feature for smithy4rs-core and add the arbitrary crate as a dependency.

# cargo.toml 

[dependencies]
# other deps...
smithy4rs-core = { version = "0.0.1", features = ["arbitrary"] }
arbitrary = { version = "1.4.2", default-features = false }

Now, use a generated shape as the input to your fuzz target:

#![allow(unused)]
fn main() {
fuzz_target!(|data: MyGeneratedStruct| {
    // Code to fuzz...
});
}

And that's it! Run cargo fuzz to execute any fuzz tests.

Unvalidated inputs

Using a generated Smithy structure for structure-aware fuzzing will always provide your fuzzed code with a structure validated by the DefaultValidator.

If you would like to generate invalid structures or use a custom validator for your fuzzed structures, the ShapeBuilder for a generated structure shape can also be used as an input to a fuzz_target closure. Such builders have not been validated and so, can be used to create invalid/custom-validated structures.