wasmCloud Smithy

wasmCloud-smithy guide

wasmCloud’s use of Smithy closely follows the Smithy IDL specification. This document is intended to be a quick reference to the major features of Smithy, and includes some conventions that wasmCloud has adopted for smithy-defined interfaces.

Contents

There is also an index of all annotations used by wasmCloud in .smithy files.

Models

We author models in Smithy IDL files, with a .smithy extension. An IDL that defines any shapes must have a namespace declaration,

A Semantic Model is built from one or more IDL files and/or json (AST model) files, and can contain multiple namespaces. To fully lint or validate a model, or to generate code from a semantic model, you need to specify paths to all dependencies in all namespaces used by the model. These dependencies are specified in a codegen.toml configuration file, usually located in the project root folder.

Data Types

In Smithy, data types are called shapes.

Supported Shapes

  • simple shapes

    • Byte, Short, Integer, Long
    • Float, Double
    • Boolean
    • String
    • Blob
    • Timestamp (* partially supported, see below)
  • aggregate shaptes

    • list List (array of any other type)
    • set Set (set of unique values)
    • map Map map of key-type to value-type)
    • structure Structure
    • service Service a collection of operations
    • operation Operation a function

The following Smithy shapes are partially supported: set, Timestamp

The following Smithy shapes are not supported (yet): BigInteger, BigDecimal, union, document. resource

Integer types

Smithy’s primitive integer types (Byte, Short, Integer, Long) are signed. The namespace org.wasmcloud.model defines unsigned types: (U8,U16,U32,U64) and, for consistency, aliases (I8,I16,I32,I64) for the signed primitive types. The unsigned types have the trait @unsignedInt, which has the trait @limit(min:0).

The @unsignedInt trait causes the code generator to generate unsigned data types in languages that support them.

Timestamp

Timestamp is currently supported only in Rust, but will be added to all supported output languages. A timestamp is represented approximately like this:

struct Timestamp {
  sec: u64,   // seconds since unix epoch in UTC (also called unix time)
  nsec: u32,  // nanoseconds since the beginning of the last whole second
}

SDK Client libraries in supported languages will include functions for converting between a Timestamp and RFC3339 strings.

Maps

Due to the way we use msgpack, map key types are limited to String.

Structures

Structures are just like the structures in your favorite programming languages.

/// Documentation for my structure
structure Point {
    x: Integer,
    y: Integer,
}

Optional and Required fields

Most structure members are optional by default.

The @box trait for a structure member means that it is not required to be present, and there is no default value. A boxed structure member would be emitted in Rust code with an Option<> wrapper.

The types boolean, byte, short, integer, long, float, and double types are not boxed unless they have an explicit @box annotation, in other words, without @box these types are required. All other types (string, list, map, structure, etc.) are implicitly boxed and without annotation would be optional.

The @required trait may be used on structure members to indicate that it must be present.

Services

A Service defines a set of operations. wasmCloud has defined a set of required and optional annotations for services and their operations.

The @wasmbus annotation is required. wasmbus is the name wasmCloud uses for its messaging protocol.

@wasmbus(
    contractId: "wasmcloud:httpclient",
    providerReceive: true )
service HttpClient {
    version: "0.1",
    operations: [ Request ]
}

This declaration states that the HttpClient service has the capability contract id wasmcloud:httpclient. The contractId declaration is required for all service providers. The boolean value providerReceive indicates that the direction of messages for this service is from actor to provider. In other words, the capability provider must implement a handler for the Request method. A service may declare actorReceive: true if the direction of messages is to an actor (either provider to actor or actor to actor). Some services can declare both actorReceive and providerReceive. (The default value for both of these fields is false, so it is only necessary to include them if their value is true.)

These attributes control code generation, so if you can’t find the generated method to send a message to your service, doublecheck that the appropriate actorReceive and providerReceive flags are enabled.

Operations

Operations represent functions, and are declared with 0 or 1 input types (parameters) and 0 or 1 output types (return values).

/// Increment the value of the counter, returning its new value
operation Increment {
    input: I32,
    output: I32,
}

Operation input and output types can be any supported data type, other than optional types. An operation with no input declaration means the operation takes no parameters, for example,

operation GetTimeOfDay {
    output: Timestamp
}

An operation without output means the operation has no return value (e.g., returns ‘void’), for example,

operation SetCounter {
    input: U64,
}

Input and output types cannot be optional. If you need to model a function such as fn lookup(key: String) -> Option<String> or func resetCounter( value: number | null ) , you’ll need to use a structure with an optional field.

Multiple parameters

We would like to model functions that take multiple parameters. We can do that with Smithy by creating a wrapper structure for the multiple args.

For example, a key value store might have a “set” operation:

Set(key: string, value: string, expires: i32): SetResponse

Since Smithy operations can only be declared with a single input type, the Smithy declaration might look like

operation Set {
  input: SetRequest,
  output: SetResponse,
}
structure SetRequest {
   @required
   key: String,
   @required
   value: String,
   @required
   expires: I32,
}
structure SetResponse {
   // ...
}

This is somewhat more verbose than we’d like it to be. In the future we would like to add an annotation to the structure to tell the code generator to “flatten” the structure into multiple input parameters when generating the function signature. This would not change the format of the data on-the-wire, so such a change would not impact binary compatibility, but it would

Documentation

Documentation for shapes is indicated by preceding the shape declaration with one or more lines of comments beginning with three slashes (/// Comment). All comments of this type are emitted by code and documentation generators, (The same effect can be achieved by using the @documentation annotation trait). In Smithy, documentation on consecutive lines is combined into a single block, and interpreted as CommonMark markdown. At the moment, html generated by the documentation generator does not perform markdown-to-html conversion, so documentation appears as it does in the source file. If the generated file is Rust source code, the Rust doc generator does convert markdown in comments, so for Rust code, markdown in the smithy comments can result in formatted comments in Rust docs.