Crates.io | homestar-wasm |
lib.rs | homestar-wasm |
version | 0.3.0 |
source | src |
created_at | 2024-01-19 21:33:53.270998 |
updated_at | 2024-03-13 19:00:29.407163 |
description | Homestar Wasm / Wasmtime implementation and IPLD <=> WIT interpreter |
homepage | |
repository | https://github.com/ipvm-wg/homestar/tree/main/homestar-wasm |
max_upload_size | |
id | 1105757 |
size | 2,915,549 |
This wasm library manages the wasmtime runtime, provides the IPLD to/from Wasm Interace Types (WIT) interpreter/translation-layer, and implements the input interface for working with Ipvm's standard Wasm tasks.
For more information, please go to our Homestar Readme.
Our recursive interpreter is able to bidirectionally translate between the runtime IPLD data model and WIT values, based on known WIT interface types.
We'll start by covering WIT primitive types.
This section outlines the translation process between IPLD boolean values
(Ipld::Bool
) and WIT bool
runtime values.
IPLD to WIT Translation:
When a WIT function expects a bool
input, an Ipld::Bool
value (either
true
or false
) is mapped to a bool
WIT runtime
value.
Example: Consider a WIT function defined as follows:
export fn: func(a: bool) -> bool;
Given a JSON input for this function:
{
"args": [true]
}
true
is converted into an Ipld::Bool
, which is then translated and
passed into fn
as a boolean argument (bool
).
WIT to IPLD Translation:
Conversely, when a boolean value is returned from a WIT function, it can be
translated back into an Ipld::Bool
.
IPLD Schema Definition:
type IPLDBooleanAsWit bool
This section outlines the translation process between IPLD integer values
(Ipld::Integer
) and WIT integer
rutime values.
The Component Model supports these integer types:
ty ::= 'u8' | 'u16' | 'u32' | 'u64'
| 's8' | 's16' | 's32' | 's64'
IPLD to WIT Translation:
Typically, when a WIT function expects an integer input, an Ipld::Integer
value is mapped to an integer WIT runtime value.
Example: Consider a WIT function defined as follows:
export fn: func(a: s32) -> s32;
Given a JSON input for this function:
{
"args": [1]
}
1
is converted into an Ipld::Integer
, which is then translated and
passed into fn
as an integer argument (s32
).
Note: However, if the input argument to the WIT interface is a float
type, but the incoming value is an Ipld::Integer
, then the IPLD value will
be cast to a float
, and remain as one for the rest of the computation. The cast is
to provide affordances for JavaScript where, for example, the number 1.0
is converted to 1
.
WIT to IPLD Translation:
Conversely, when an integer value (not a float) is returned from a WIT
function, it can be translated back into an Ipld::Integer
.
IPLD Schema Definitions:
type IPLDIntegerAsWit union {
| U8 int
| U16 int
| U32 int
| U64 int
| S8 int
| S16 int
| S32 int
| S64 int
| Float32In int
| Float64In int
} representation kinded
type WitAsIpldInteger union {
| U8 int
| U16 int
| U32 int
| U64 int
| S8 int
| S16 int
| S32 int
| S64 int
| Float32Out float
| Float64Out float
} representation kinded
This section outlines the translation process between IPLD float values
(Ipld::Float
) and WIT float
runtime values.
The Component Model supports these Float types:
ty ::= 'float32' | 'float64'
IPLD to WIT Translation:
Typically, when a WIT function expects a float input, an Ipld::Float
value is
mapped to a float WIT runtime value. Casting is done to convert from f32
to
f64
if necessary.
Example: Consider a WIT function defined as follows:
export fn: func(a: f64) -> f64;
Given a JSON input for this function:
{
"args": [1.0]
}
1.0
is converted into an Ipld::Float
, which is then translated and
passed into fn
as a float argument (f64
).
Note: However, if the input argument to the WIT interface is one of the
WIT interger types, but the incoming value is an Ipld::Integer
, then the
IPLD value will be cast to that integer type, and remain as one for the rest
of the computation.
WIT to IPLD Translation:
Conversely, when a float32
or float64
value is returned from a WIT
function, it can be translated back into an Ipld::Float
.
Note: In converting from float32
to float64
, the latter of which is
the default precision for IPLD, precision will be lost.
The interpreter will use decimal precision in this conversion.
IPLD Schema Definitions:
type IPLDFloatAsWit union {
| Float32 float
| Float64 float
} representation kinded
type WitAsIpldFloat union {
| Float32 float
| Float64 float
} representation kinded
This section outlines the translation process between IPLD string values
(Ipld::String
) and various WIT runtime values. A Ipld::String
value can be
interpreted as one of a string
, char
, or an enum
discriminant
(which has no payload).
string
IPLD to WIT Translation
When a WIT function expects a string
input, an Ipld::String
value is
mapped to a string
WIT runtime value.
Example:
export fn: func(a: string) -> string;
Given a JSON input for this function:
{
"args": ["Saspirilla"]
}
"Saspirilla"
is converted into an Ipld::String
, which is then translated
and passed into fn
as a string argument (string
).
WIT to IPLD Translation:
Conversely, when a string
value is returned from a WIT function, it is
translated back into an Ipld::String
.
char
IPLD to WIT Translation
When a WIT function expects a char
input, an Ipld::String
value is
mapped to a char
WIT runtime value.
Example:
export fn: func(a: char) -> char;
Given a JSON input for this function:
{
"args": ["S"]
}
"S"
is converted into an Ipld::String
, which is then translated and
passed into fn
as a char argument (char
).
WIT to IPLD Translation:
Conversely, when a char value is returned from a WIT function, it is
translated back into an Ipld::String
.
enum
:
An enum statement defines a new type which is semantically equivalent to a variant where none of the cases have a payload type.
IPLD to WIT Translation
When a WIT function expects an enum
input, an Ipld::String
value is
mapped to a enum
WIT runtime value.
Example:
enum color {
Red,
Green,
Blue
}
export fn: func(a: color) -> string;
Given a JSON input for this function:
{
"args": ["Green"]
}
"Green"
is converted into an Ipld::String
, which is then translated and
passed into fn
as a enum argument (color
). You'll have to provide a
string that matches on one of the discriminants.
WIT to IPLD Translation:
Conversely, when an enum value is returned from a WIT function, it can be
translated back into an Ipld::String
value.
IPLD Schema Definitions:
type Enum enum {
| Red
| Green
| Blue
}
type IPLDStringAsWit union {
| Enum Enum
| String string
| Char string
| Listu8In string
} representation kinded
type WitAsIpldString union {
| Enum Enum
| String string
| Char string
| Listu8Out bytes
} representation kinded
This section outlines the translation process between IPLD bytes values
(Ipld::Bytes
) and various WIT runtime values. A Ipld::Bytes
value
can be interpreted either as a list<u8>
or string
.
list
:
IPLD to WIT Translation
When a WIT function expects a list<u8>
input, an Ipld::Bytes
value is
mapped to a list<u8>
WIT runtime value.
Example:
export fn: func(a: list<u8>) -> list<u8>;
Given a JSON input for this function:
{
"args": [{"/": {"bytes": "aGVsbDA"}}]
}
"aGVsbDA"
is converted into an Ipld::Bytes
, which is then translated
into bytes and passed into fn
as a list<u8>
argument.
WIT to IPLD Translation:
Conversely, when a list<u8>
value is returned from a WIT function, it is
translated back into an Ipld::Bytes
value if the list contains valid
u8
values.
string
IPLD to WIT Translation
When a WIT function expects a string
input, an Ipld::Bytes
value is
mapped to a string
WIT runtime value.
Example:
export fn: func(a: string) -> string;
Given a JSON input for this function:
{
"args": [{"/": {"bytes": "aGVsbDA"}}]
}
"aGVsbDA"
is converted into an Ipld::Bytes
, which is then translated
into a string
and passed into fn
as a string
argument.
WIT to IPLD Translation:
Here, when a string value is returned from a WIT function, it is
translated into an Ipld::String
value, because we can't determine if it
was originally bytes
.
IPLD Schema Definitions:
type IPLDBytesAsWit union {
| ListU8 bytes
| StringIn bytes
} representation kinded
type WitAsIpldBytes union {
| ListU8 bytes
| StringOut string
} representation kinded
This section outlines the translation process between IPLD null values
(Ipld::Null
) and various WIT runtime values. A Ipld::Null
value
can be interpreted either as a string
or option
.
We'll cover only the string
case here and return to the option
case
below.
IPLD to WIT Translation
When a WIT function expects a string
input, an Ipld::Null
value is
mapped as a "null"
string
WIT runtime value.
Example:
export fn: func(a: string) -> string;
Given a JSON input for this function:
{
"args": [null]
}
null
is converted into an Ipld::Null
, which is then translated and
passed into fn
as a string
argument with the value of "null"
.
WIT to IPLD Translation:
Conversely, when a string
value of "null"
is returned from a WIT function,
it can be translated into an Ipld::Null
value.
IPLD Schema Definitions:
type None unit representation null
type IPLDNullAsWit union {
| None
| String string
} representation kinded
type WitAsIpldNull union {
| None
| String string
} representation kinded
This section outlines the translation process between IPLD link values
(Ipld::Link
) and WIT string
runtime values. A Ipld::Link
is always
interpreted as a string
in WIT, and vice versa.
IPLD to WIT Translation
When a WIT function expects a string
input, an Ipld::Link
value is
mapped to a string
WIT runtime value, translated accordingly based
on the link being Cidv0 or Cidv1.
Example:
export fn: func(a: string) -> string;
Given a JSON input for this function:
{
"args": ["bafybeia32q3oy6u47x624rmsmgrrlpn7ulruissmz5z2ap6alv7goe7h3q"]
}
"bafybeia32q3oy6u47x624rmsmgrrlpn7ulruissmz5z2ap6alv7goe7h3q"
is converted
into an Ipld::Link
, which is then translated and passed into fn
as a
string
argument.
WIT to IPLD Translation:
Conversely, when a string
value is returned from a WIT function, and if it
can be converted to a Cid, it can then be translated into an Ipld::Link
value.
IPLD Schema Definitions:
type IPLDLinkAsWit &String link
type WitAsIpldLink &String link
Next, we'll cover the more interesting, WIT non-primitive types.
This section outlines the translation process between IPLD list values
(Ipld::List
) and various WIT runtime values. A Ipld::List
value can be interpreted as one of a list
, tuple
, set of flags
,
or a result
.
We'll return to the result
case below, and cover the rest of the
possibilities here.
IPLD to WIT Translation
When a WIT function expects a list
input, an Ipld::List
value is
mapped to a list
WIT runtime value.
Example:
export fn: func(a: list<s32>, b: s32) -> list<s32>;
Given a JSON input for this function:
{
"args": [[1, 2, 3], 44]
}
[1, 2, 3]
is converted into an Ipld::List
, which is then translated
and passed into fn
as a list<s32>
argument.
WIT to IPLD Translation:
Conversely, when a list
value is returned from a WIT function, it is
translated back into an Ipld::List
value.
IPLD to WIT Translation
When a WIT function expects a tuple
input, an Ipld::List
value is
mapped to a tuple
WIT runtime value.
Example:
type ipv6-socket-address = tuple<u16, u16, u16, u16, u16, u16, u16, u16>;
export fn: func(a: ipv6-socket-address) -> tuple<u32, u32>;
Given a JSON input for this function:
{
"args": [[8193, 3512, 34211, 0, 0, 35374, 880, 29492]]
}
[8193, 3512, 34211, 0, 0, 35374, 880, 29492]
is converted into an
Ipld::List
, which is then translated and passed into fn
as a
tuple<u16, u16, u16, u16, u16, u16, u16, u16>
argument.
If the length of list does not match not match the number of fields in the tuple interface type, then an error will be thrown in the interpreter.
WIT to IPLD Translation:
Conversely, when a tuple
value is returned from a WIT function, it is
translated back into an Ipld::List
value.
flags
represent a bitset structure with a name for each bit. The type
represents a set of named booleans. In an instance of the named type, each flag will
be either true or false.
IPLD to WIT Translation
When a WIT function expects a flags
input, an Ipld::List
value is
mapped to a flags
WIT runtime value.
When used as an input, you can set the flags you want turned on/true as an inclusive subset of strings. When used as an output, you will get a list of strings representing the flags that are set to true.
Example:
flags permissions {
read,
write,
exec,
}
export fn: func(perm: permissions) -> bool;
Given a JSON input for this function:
{
"args": [["read", "write"]]
}
[read, write]
is converted into an Ipld::List
, which is then translated
and passed into fn
as a permissions
argument.
WIT to IPLD Translation:
Conversely, when a flags
value is returned from a WIT function, it is
translated back into an Ipld::List
value.
IPLD Schema Definitions:
type IPLDListAsWit union {
| List [any]
| Tuple [any]
| Flags [string]
} representation kinded
type WitAsIpldList union {
| List [any]
| Tuple [any]
| Flags [string]
} representation kinded
This section outlines the translation process between IPLD map values
(Ipld::Map
) and various WIT runtime values. A Ipld::Map
value can be interpreted as one of a record
, variant
, or
a list
of two-element tuples
.
IPLD to WIT Translation
When a WIT function expects a record
input, an Ipld::Map
value is
mapped to a record
WIT runtime value.
Example:
record pair {
x: u32,
y: u32,
}
export fn: func(a: pair) -> u32;
Given a JSON input for this function:
{
"args": [{"x": 1, "y": 2}]
}
{"x": 1, "y": 2}
is converted into an Ipld::Map
, which is then
translated and passed into fn
as a pair
argument.
The keys in the map must match the field names in the record type.
WIT to IPLD Translation:
Conversely, when a record
value is returned from a WIT function, it is
translated back into an Ipld::Map
value.
A variant statement defines a new type where instances of the type match exactly one of the variants listed for the type. This is similar to a "sum" type in algebraic datatypes (or an enum in Rust if you're familiar with it). Variants can be thought of as tagged unions as well.
Each case of a variant can have an optional type / payload associated with it which is present when values have that particular case's tag.
IPLD to WIT Translation
When a WIT function expects a variant
input, an Ipld::Map
value is
mapped to a variant
WIT runtime value.
Example:
variant filter {
all,
none,
some(list<string>),
}
export fn: func(a: filter);
Given a JSON input for this function:
{
"args": [{"some" : ["a", "b", "c"]}]
}
{"some" : ["a", "b", "c"]}
is converted into an Ipld::Map
, which is
then translated and passed into fn
as a filter
argument, where the key
is the variant name and the value is the payload.
The keys in the map must match the variant names in the variant type.
WIT to IPLD Translation:
Conversely, when a variant
value is returned from a WIT function, it is
translated back into an Ipld::Map
value where the tag is the key and
payload is the value.
list
:
IPLD to WIT Translation
When a WIT function expects a nested list
of two-element tuples
as input,
an Ipld::Map
value is mapped to that specific WIT runtime value.
Example:
export fn: func(a: list<tuple<string, u32>>) -> list<u32>;
Given a JSON input for this function:
{
"args": [{"a": 1, "b": 2}]
}
{"a": 1, "b": 2}
is converted into an Ipld::Map
, which is then
translated and passed into fn
as a list<tuple<string, u32>>
argument.
WIT to IPLD Translation:
Conversely, when a list
of two-element tuples
is returned from a WIT
function, it can be translated back into an Ipld::Map
value.
IPLD Schema Definitions:
type TupleAsMap {string:any} representation listpairs
type IPLDMapAsWit union {
| Record {string:any}
| Variant {string:any}
| List TupleAsMap
} representation kinded
type WitAsIpldMap union {
| Record {string:any}
| Variant {string:any}
| List TupleAsMap
} representation kinded
This section outlines the translation process between WIT option runtime values
(of type option
) and various IPLD values. An option
can be interpreted
as either a Ipld::Null
or of any other IPLD value.
IPLD to WIT Translation
When a WIT function expects an option
as input, an Ipld::Null
value is
mapped to the None
/Unit
case for a WIT option. Otherwise, any other IPLD
value will be mapped to its matching WIT runtime value directly.
Example:
export fn: func(a: option<s32>) -> option<s32>;
Some
case:
Json Input:
{
"args": [1]
}
None
case:
Json Input:
{
"args": [null]
}
1
is converted into an Ipld::Integer
, which is then translated and
passed into fn
as an integer argument (s32
), as the Some
case of the
option.
null
is converted into an Ipld::Null
, which is then translated and
passed into fn
as a None
/Unit
case of the option (i.e. no value in WIT).
Essentially, you can view this as Ipld::Any
being the Some
case and
Ipld::Null
being the None
case.
WIT to IPLD Translation:
Conversely, when an option
value is returned from a WIT function, it can be
translated back into an Ipld::Null
value if it's the None
/Unit
case, or
any other IPLD value if it's the Some
case.
IPLD Schema Definitions:
type IpldAsWitOption union {
| Some any
| None
} representation kinded
type WitAsIpldOption union {
| Some any
| None
} representation kinded
This section outlines the translation process between WIT result runtime values
(of type result
) and various IPLD values. We treat result as Left/Right
either types over an Ipld::List
of two elements.
A result
can be interpreted as one of these patterns:
Ok
(with a payload)
IPLD to WIT Translation
When a WIT function expects a result
as input, an Ipld::List
value can
be mapped to the Ok
case of the result
WIT runtime value, including
a payload.
Example:
export fn: func(a: result<s32, string>) -> result<s32, string>;
Given a JSON input for this function:
{
"args": [[47, null]]
}
[47, null]
is converted into an Ipld::List
, which is then translated
and passed into fn
as an Ok
case of the result
argument with a
payload of 47
matching the s32
type on the left.
WIT to IPLD Translation:
Conversely, when a result
value is returned from a WIT function, it can
be translated back into an Ipld::List
of this specific structure.
Err
(with a payload)
IPLD to WIT Translation
Example:
export fn: func(a: result<s32, string>) -> result<s32, string>;
Given a JSON input for this function:
{
"args": [[null, "error message"]]
}
[null, "error message"]
is converted into an Ipld::List
, which is
then translated and passed into fn
as an Err
case of the result
argument with a payload of "error message"
matching the string
type
on the right.
WIT to IPLD Translation:
Conversely, when a result
value is returned from a WIT function, it can
be translated back into an Ipld::List
of this specific structure.
Ok
case (without a payload)
IPLD to WIT Translation
Example:
export fn: func(a: result<_, string>) -> result<_, string>;
Given a JSON input for this function:
{
"args": [[47, null]]
}
[47, null]
is converted into an Ipld::List
, which is then translated
and passed into fn
as an Ok
case of the result
argument. The payload
is ignored as it's not needed (expressed in the type as _
above), so
47
is not used.
WIT to IPLD Translation:
Here, when this specific Ok
case is returned from a WIT function, it can
be translated back into an Ipld::List
, but one structured as
[1, null]
internally, which signifies the Ok
(not error) case, with
the 1
payload discarded.
Err
case (without a payload)
IPLD to WIT Translation
Example:
export fn: func(a: result<s32, _>) -> result<s32, _>;
Given a JSON input for this function:
{
"args": [[null, "error message"]]
}
[null, "error message"]
is converted into an Ipld::List
, which is
then translated and passed into fn
as an Err
case of the result
argument. The payload is ignored as it's not needed (expressed in the type
as _
above), so "error message"
is not used.
WIT to IPLD Translation:
Here, when this specific Err
case is returned from a WIT function, it
can be translated back into an Ipld::List
, but one structured as
[null, 1]
internally, which signifies the Err
(error) case, with
the 1
payload discarded.
IPLD Schema Definitions:
type Null unit representation null
type IpldAsWitResult union {
| Ok [any, Null]
| Err [Null, any]
} representation kinded
type WitAsIpldResult union {
| Ok [any, Null]
| OkNone [1, Null]
| Err [Null, any]
| ErrNone [Null, 1]
} representation kinded
Note: any
is used here to represent any type that's not Null
. So,
given an input with a result
type, the JSON value of
{
"args": [null, null]
}
will fail to be translated into a Wit result
runtime value, as it's ambiguous
which case it should be mapped to.