diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf2f65c..7816718 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,27 +16,32 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-is - - name: Setup Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - name: Check formatting - run: cargo fmt -- --check - - name: Clippy - run: cargo clippy -- -Dwarnings - - name: Tests - run: cargo test --all-features + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-is + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - uses: cargo-bins/cargo-binstall@main + - name: Install cargo-component + run: cargo binstall --no-confirm cargo-component@0.7.0 + - name: Check formatting + run: cargo fmt -- --check + - name: Clippy + run: cargo clippy -- -Dwarnings + - name: Tests + run: cargo test --all-features + - name: Build golem-rust component + run: cargo component build -p golem-rust publish: needs: [ build ] @@ -64,10 +69,12 @@ jobs: override: true - id: get_version uses: battila7/get-version-action@v2 - - name: Publish crate + - name: Publish crates env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} run: | export VERSION="${{ steps.get_version.outputs.version-without-v }}" - sed -i "s/0.0.0/$VERSION/g" Cargo.toml - cargo publish --all-features --allow-dirty + sed -i "s/0.0.0/$VERSION/g" golem-rust-macro/Cargo.toml + sed -i "s/0.0.0/$VERSION/g" golem-rust/Cargo.toml + cargo publish -p golem-rust-macro --all-features --allow-dirty + cargo publish -p golem-rust --all-features --allow-dirty diff --git a/Cargo.lock b/Cargo.lock index 842cf11..b6f066f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,38 +2,83 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "golem-rust" -version = "0.1.0" +version = "0.0.0" +dependencies = [ + "golem-rust-macro", + "uuid", + "wit-bindgen", +] + +[[package]] +name = "golem-rust-macro" +version = "0.0.0" dependencies = [ + "heck", "proc-macro2", "quote", "syn", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "syn" -version = "2.0.43" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -45,3 +90,27 @@ name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wit-bindgen" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6237168d93497b26dacdab157b08ad2787d74cdce10f89735f791b2a225eba4d" +dependencies = [ + "bitflags", +] diff --git a/Cargo.toml b/Cargo.toml index f29a3c1..57f3c95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,13 @@ -[package] -name = "golem-rust" -version = "0.1.0" -edition = "2021" -license = "Apache-2.0" -homepage = "https://golem.cloud" -description = "Golem Rust tooling library that facilitates writing Golem backends in Rust" +[workspace] +resolver = "2" -[lib] -proc-macro = true -path = "src/lib.rs" +members = [ + "golem-rust-macro", + "golem-rust", +] + +[profile.release] +debug = false +lto = true +opt-level = 's' -[dependencies] -proc-macro2 = "1.0.70" -quote = "1.0.33" -syn = { version = "2.0.39", features = ["extra-traits", "full", "fold"] } \ No newline at end of file diff --git a/README.md b/README.md index 1c5ba62..c31f7a3 100644 --- a/README.md +++ b/README.md @@ -1,283 +1,13 @@ -# Golem Rust +# golem-rust -This crate contains couple of Rust macros that facilitate writing Golem Cloud backends in Rust: -1. Derives `From<>` and `Into<>` typeclasses between wit-bindgen derived data types and custom domain model data types. -2. Generates wit file from rust code. +This repository contains Rust crates that help writing [Golem](https://golem.cloud) programs. -## Add to your project +## golem-rust -```shell -$ cargo add golem-rust -``` +The `golem-rust` crate contains Rust wrappers for Golem's runtime API, including +the [transaction API](https://learn.golem.cloud/docs/transaction-api). -## 1. Convert between generated data types and custom domain model +## golem-rust-macro -When working with WIT files in Golem, wit-bindgen library generates data types based on the wit file. There are few drawbacks when using these data types, so very often, user would create its own data types. In order to easily convert between generated and domain data types, programmer needs to implement boilerplate-y `From<>` and `Into<>` typeclasses. - -This project contains macro that would automatically implement those typeclasses. - -### Struct - -Let's say we have -```rust - pub struct Person { - pub name: String, - pub age: i32, - } - - pub struct WitPerson { - pub name: String, - pub age: i32, - } - -``` - -We can use macro help in implementing `From` and `Into` typeclasses by annotating Person with `#[derive(golem_rust::WIT_From_Into))]` - -```rust - #[derive(golem_rust::WIT_From_Into))] - pub struct Person { - pub name: String, - pub age: i32, - } -``` - -then the following code compiles without problems - -```rust - let me = Person { - name: "Jaro".to_owned(), - age: 32, - }; - - let converted: WitPerson = me.into(); -``` - -#### Custom data type names - -The above macro assumed that the data type for which we are deriving `From<>` and `Into<>` is called `WitPerson`. By default macro assumes that the name of the data type is `Wit` + annotated data type name. In case the name is different, we need to add `#[wit_type_name(DerivedName)]` attribute. - -```rust - #[derive(golem_rust::WIT_From_Into))] - #[wit_type_name(DerivedName)] - pub struct Person { - pub name: String, - pub age: i32, - } -``` - -#### Renaming of fields - -In case the field names in derived data type are different we can use field attribute `#[rename_field("")]` - - -```rust - #[derive(golem_rust::WIT_From_Into))] - #[wit_type_name(WitPerson)] - pub struct Person { - - #[rename_field("name2")] - pub name: String, - - #[rename_field("age2")] - pub age: i32, - } -``` - -### Enums - -Very similar to structs, let's say we have the following enum data type: - - -```rust - #[derive(golem_rust::WIT_From_Into))] - #[wit_type_name(SimilarColors)] - pub enum Colors { - Red, - White, - - #[rename_field("Yellow2")] - Yellow, - } - - pub enum SimilarColors { - Red, - White, - Yellow2, - } -``` - -Then very simply we can use `.into()` and it will compile. - -```rust - let yellow = Colors::Yellow; - - let wit_collors: SimilarColors = yellow.into(); -``` - -More examples can be found in `golem-rust-example/src/main.rs` - -## 2. Generate WIT file from rust module. - -Let's say we are building auction app powered by Golem Cloud. We would like to support some basic functionality like: -- initializing an auction -- get all auctions -- close auctions -- create a bidder -- make a bid -Also we need some data types like describing auction, bidder, result and so on. - -The WIT file itself could look like this: - -``` -package auction:app - -interface api { - - record bidder-id { - bidder-id: string, - } - - record auction-id { - auction-id: string, - } - - record auction { - auction-id: auction-id, - name: string, - description: string, - starting-price: float32, - deadline: deadline, - } - - variant bid-result { - failure(string), - success - } - - type deadline = u64 - - - initialize: func(auction: auction) - - - bid: func(bidder-id: bidder-id, price: float32) -> bid-result - - - close-auction: func() -> option - - - create-bidder: func(name: string, address: string) -> bidder-id - - - create-auction: func(name: string, description: string, starting-price: float32, deadline: u64) -> auction-id - - - get-auctions: func() -> list - -} - -world golem-service { - export api -} -``` - -There are many things that could go wrong when writing this, especially if you're not familiar with WIT. But mostly, it's just a boilerplate that can now be avoided. - -Simply annotate your inner module with `#[golem_rust::create_wit_file]` macro. - -```rust -#[golem_rust::create_wit_file] -mod auction_app { - - struct BidderId { - bidder_id: String - } - - struct AuctionId { - auction_id: String - } - - struct Auction { - auction_id: AuctionId, - name: String, - description: String, - starting_price: f32, - deadline: Deadline, - } - - enum BidResult { - Failure(String), - Success - } - - type Deadline = u64; - - trait AuctionService { - - fn initialize(auction: Auction); - - fn bid(bidder_id: BidderId, price: f32) -> BidResult; - - fn close_auction() -> Option; - - fn create_bidder(name: String, address: String) -> BidderId; - - fn create_auction(name: String, description: String, starting_price: f32, deadline: u64) -> AuctionId; - - fn get_auctions() -> Vec; - } -} -``` - -and this will generate `generated.wit` file in the root of your project. -If you want your generated file to have custom name, add the name to the attribute e.g. `#[golem_rust::create_wit_file("auction_app_file.wit")]` - -### WIT file generation details - -The following empty inner module - -```rust -#[golem_rust::create_wit_file] -mod package_name { - -} -``` - -translates to the empty with file with package name derived from module name: - -``` -package package:name - -interface api { - -} - -world golem-service { - export api -} -``` -So interface name is always `api` which is exported from `world geolem-service` - -Other rules of wit file generation: -- Rust `struct` is translated into WIT `record`. -- `Enum` is translated into either `variant` or `enum` depending on whether enum has associated data. -- `Option<>` is `option<>`. -- array and `vec<>` is `list<>`. -- type aliases `type Name = String` becomes `type name = string` -- `Box<>` is ignored and inner type is taken care of. -- tuples are supported. -- PascalCase is replaced with kebab-case. -- snake_case is replaced with kebab-case. -- Trait name does not matter. -- Functions inside trait are translated to WIT file functions. -- It has to be inner module and all used types need to be defined inside module. -- If there are multiple traits inside module, their content is concatenated into single wit interface. - -## How to contribute - -Contributions very are welcome. If you find a bug, use case that is not supported or you simply think that error message is not good enough, please open an issue or submit a PR. This library is still at an early stage of development and although some use cases are covered, feedback would be very helpful for polishing this library. - -## golem-rust-examples - -Inner binary project which depends on golem-rust. Here you can find more examples on how to use golem-rust. \ No newline at end of file +The `golem-rust-macro` crate contains Rust macros for working with generated bindings and implementing a code-first +approach for component interfaces. diff --git a/golem-rust-macro/Cargo.toml b/golem-rust-macro/Cargo.toml new file mode 100644 index 0000000..e6ecb7d --- /dev/null +++ b/golem-rust-macro/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "golem-rust-macro" +version = "0.0.0" +edition = "2021" +license = "Apache-2.0" +homepage = "https://golem.cloud" +description = "Golem Rust tooling library that facilitates writing Golem backends in Rust" + +[lib] +proc-macro = true +path = "src/lib.rs" + +[dependencies] +heck = "0.5.0" +proc-macro2 = "1.0.79" +quote = "1.0.36" +syn = { version = "2.0.58", features = ["extra-traits", "full", "fold"] } \ No newline at end of file diff --git a/golem-rust-macro/README.md b/golem-rust-macro/README.md new file mode 100644 index 0000000..1c5ba62 --- /dev/null +++ b/golem-rust-macro/README.md @@ -0,0 +1,283 @@ +# Golem Rust + +This crate contains couple of Rust macros that facilitate writing Golem Cloud backends in Rust: +1. Derives `From<>` and `Into<>` typeclasses between wit-bindgen derived data types and custom domain model data types. +2. Generates wit file from rust code. + +## Add to your project + +```shell +$ cargo add golem-rust +``` + +## 1. Convert between generated data types and custom domain model + +When working with WIT files in Golem, wit-bindgen library generates data types based on the wit file. There are few drawbacks when using these data types, so very often, user would create its own data types. In order to easily convert between generated and domain data types, programmer needs to implement boilerplate-y `From<>` and `Into<>` typeclasses. + +This project contains macro that would automatically implement those typeclasses. + +### Struct + +Let's say we have +```rust + pub struct Person { + pub name: String, + pub age: i32, + } + + pub struct WitPerson { + pub name: String, + pub age: i32, + } + +``` + +We can use macro help in implementing `From` and `Into` typeclasses by annotating Person with `#[derive(golem_rust::WIT_From_Into))]` + +```rust + #[derive(golem_rust::WIT_From_Into))] + pub struct Person { + pub name: String, + pub age: i32, + } +``` + +then the following code compiles without problems + +```rust + let me = Person { + name: "Jaro".to_owned(), + age: 32, + }; + + let converted: WitPerson = me.into(); +``` + +#### Custom data type names + +The above macro assumed that the data type for which we are deriving `From<>` and `Into<>` is called `WitPerson`. By default macro assumes that the name of the data type is `Wit` + annotated data type name. In case the name is different, we need to add `#[wit_type_name(DerivedName)]` attribute. + +```rust + #[derive(golem_rust::WIT_From_Into))] + #[wit_type_name(DerivedName)] + pub struct Person { + pub name: String, + pub age: i32, + } +``` + +#### Renaming of fields + +In case the field names in derived data type are different we can use field attribute `#[rename_field("")]` + + +```rust + #[derive(golem_rust::WIT_From_Into))] + #[wit_type_name(WitPerson)] + pub struct Person { + + #[rename_field("name2")] + pub name: String, + + #[rename_field("age2")] + pub age: i32, + } +``` + +### Enums + +Very similar to structs, let's say we have the following enum data type: + + +```rust + #[derive(golem_rust::WIT_From_Into))] + #[wit_type_name(SimilarColors)] + pub enum Colors { + Red, + White, + + #[rename_field("Yellow2")] + Yellow, + } + + pub enum SimilarColors { + Red, + White, + Yellow2, + } +``` + +Then very simply we can use `.into()` and it will compile. + +```rust + let yellow = Colors::Yellow; + + let wit_collors: SimilarColors = yellow.into(); +``` + +More examples can be found in `golem-rust-example/src/main.rs` + +## 2. Generate WIT file from rust module. + +Let's say we are building auction app powered by Golem Cloud. We would like to support some basic functionality like: +- initializing an auction +- get all auctions +- close auctions +- create a bidder +- make a bid +Also we need some data types like describing auction, bidder, result and so on. + +The WIT file itself could look like this: + +``` +package auction:app + +interface api { + + record bidder-id { + bidder-id: string, + } + + record auction-id { + auction-id: string, + } + + record auction { + auction-id: auction-id, + name: string, + description: string, + starting-price: float32, + deadline: deadline, + } + + variant bid-result { + failure(string), + success + } + + type deadline = u64 + + + initialize: func(auction: auction) + + + bid: func(bidder-id: bidder-id, price: float32) -> bid-result + + + close-auction: func() -> option + + + create-bidder: func(name: string, address: string) -> bidder-id + + + create-auction: func(name: string, description: string, starting-price: float32, deadline: u64) -> auction-id + + + get-auctions: func() -> list + +} + +world golem-service { + export api +} +``` + +There are many things that could go wrong when writing this, especially if you're not familiar with WIT. But mostly, it's just a boilerplate that can now be avoided. + +Simply annotate your inner module with `#[golem_rust::create_wit_file]` macro. + +```rust +#[golem_rust::create_wit_file] +mod auction_app { + + struct BidderId { + bidder_id: String + } + + struct AuctionId { + auction_id: String + } + + struct Auction { + auction_id: AuctionId, + name: String, + description: String, + starting_price: f32, + deadline: Deadline, + } + + enum BidResult { + Failure(String), + Success + } + + type Deadline = u64; + + trait AuctionService { + + fn initialize(auction: Auction); + + fn bid(bidder_id: BidderId, price: f32) -> BidResult; + + fn close_auction() -> Option; + + fn create_bidder(name: String, address: String) -> BidderId; + + fn create_auction(name: String, description: String, starting_price: f32, deadline: u64) -> AuctionId; + + fn get_auctions() -> Vec; + } +} +``` + +and this will generate `generated.wit` file in the root of your project. +If you want your generated file to have custom name, add the name to the attribute e.g. `#[golem_rust::create_wit_file("auction_app_file.wit")]` + +### WIT file generation details + +The following empty inner module + +```rust +#[golem_rust::create_wit_file] +mod package_name { + +} +``` + +translates to the empty with file with package name derived from module name: + +``` +package package:name + +interface api { + +} + +world golem-service { + export api +} +``` +So interface name is always `api` which is exported from `world geolem-service` + +Other rules of wit file generation: +- Rust `struct` is translated into WIT `record`. +- `Enum` is translated into either `variant` or `enum` depending on whether enum has associated data. +- `Option<>` is `option<>`. +- array and `vec<>` is `list<>`. +- type aliases `type Name = String` becomes `type name = string` +- `Box<>` is ignored and inner type is taken care of. +- tuples are supported. +- PascalCase is replaced with kebab-case. +- snake_case is replaced with kebab-case. +- Trait name does not matter. +- Functions inside trait are translated to WIT file functions. +- It has to be inner module and all used types need to be defined inside module. +- If there are multiple traits inside module, their content is concatenated into single wit interface. + +## How to contribute + +Contributions very are welcome. If you find a bug, use case that is not supported or you simply think that error message is not good enough, please open an issue or submit a PR. This library is still at an early stage of development and although some use cases are covered, feedback would be very helpful for polishing this library. + +## golem-rust-examples + +Inner binary project which depends on golem-rust. Here you can find more examples on how to use golem-rust. \ No newline at end of file diff --git a/src/der_macro.rs b/golem-rust-macro/src/expand.rs similarity index 100% rename from src/der_macro.rs rename to golem-rust-macro/src/expand.rs diff --git a/src/lib.rs b/golem-rust-macro/src/lib.rs similarity index 90% rename from src/lib.rs rename to golem-rust-macro/src/lib.rs index 3a4bac8..3494365 100644 --- a/src/lib.rs +++ b/golem-rust-macro/src/lib.rs @@ -12,12 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod der_macro; -mod wit_gen; - use proc_macro::TokenStream; + use syn::*; +use crate::transaction::golem_operation_impl; + +mod expand; +mod transaction; +mod wit_gen; + /// Derives `From<>` And `Into<>` typeclasses for wit-bindgen generated data types (e.g. `WitPerson`) /// and custom domain data types (e.g. `Person`). So it's possible to write code like this: /// ```ignore @@ -41,7 +45,7 @@ use syn::*; /// } /// /// -/// #[derive(golem_rust::WIT_From_Into)] +/// #[derive(golem_rust_macro::WIT_From_Into)] /// #[wit_type_name(WitPerson)] /// pub struct Person { /// @@ -55,17 +59,17 @@ use syn::*; pub fn derive(input: TokenStream) -> TokenStream { let mut input = parse_macro_input!(input as DeriveInput); - der_macro::expand_wit(&mut input) + expand::expand_wit(&mut input) .unwrap_or_else(syn::Error::into_compile_error) .into() } -/// Annotates a module with `#[golem_rust::create_wit_file]` and generates WIT file in the root of your project. +/// Annotates a module with `#[golem_rust_macro::create_wit_file]` and generates WIT file in the root of your project. /// Supports enums, structs, traits and alias types. /// /// # Example: /// ``` -/// #[golem_rust::create_wit_file("auction_app.wit")] +/// #[golem_rust_macro::create_wit_file("auction_app.wit")] /// mod auction_app { /// /// struct BidderId { @@ -189,3 +193,9 @@ pub fn create_wit_file(_attr: TokenStream, item: TokenStream) -> TokenStream { .unwrap_or_else(syn::Error::into_compile_error) .into() } + +/// Defines a function as an `Operation` that can be used in transactions +#[proc_macro_attribute] +pub fn golem_operation(attr: TokenStream, item: TokenStream) -> TokenStream { + golem_operation_impl(attr, item) +} diff --git a/golem-rust-macro/src/transaction.rs b/golem-rust-macro/src/transaction.rs new file mode 100644 index 0000000..b1d53ed --- /dev/null +++ b/golem-rust-macro/src/transaction.rs @@ -0,0 +1,160 @@ +// Copyright 2024 Golem Cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use heck::ToPascalCase; +use proc_macro::TokenStream; +use proc_macro2::Ident; + +use quote::quote; +use syn::punctuated::Punctuated; +use syn::{parse_macro_input, parse_quote, FnArg, ItemFn, Meta, PatType, ReturnType, Type}; + +pub fn golem_operation_impl(args: TokenStream, item: TokenStream) -> TokenStream { + let args = parse_macro_input!(args with Punctuated::::parse_terminated); + + let mut compensation = None; + for arg in args { + if let Meta::NameValue(name_value) = arg { + let name = name_value.path.get_ident().unwrap().to_string(); + let value = name_value.value; + + if name == "compensation" { + compensation = Some(value); + } + } + } + + let ast: ItemFn = syn::parse(item).unwrap(); + let mut fnsig = ast.sig.clone(); + + let (succ, err) = match fnsig.output { + ReturnType::Type(_, ref typ) => result_type(typ), + _ => panic!("Expected function to have a return type of Result<_, _>"), + } + .expect("Expected function to have a return type of Result<_, _>"); + + let inputs: Vec = fnsig.inputs.iter().cloned().collect(); + let mut input_names = Vec::new(); + let mut input_types = Vec::new(); + for input in inputs.iter() { + match input { + FnArg::Typed(PatType { pat, ty, .. }) => { + input_names.push(pat.clone()); + input_types.push(ty.clone()); + } + FnArg::Receiver(_) => panic!("Expected function to have no self argument"), + } + } + let input_pattern: proc_macro2::TokenStream = quote! { + (#(#input_names),*): (#(#input_types),*) + }; + let input_args: Vec = + input_names.iter().map(|name| quote! { #name }).collect(); + + let compensate = match &compensation { + Some(_) => quote! { golem_rust::call_compensation_function }, + None => quote! {}, + }; + + let compensation_pattern = quote! { #input_pattern, op_result: #succ }; + let compensation_args = input_args.clone(); + + let operation = quote! { operation }; + + fnsig.inputs.insert( + 0, + parse_quote! { + self + }, + ); + + match fnsig.output { + ReturnType::Type(_, ref mut typ) => { + *typ = parse_quote! { Result<#succ, #err> }; + } + _ => panic!("Expected function to have a return type of Result<_, _>"), + }; + + let fnname = fnsig.ident.clone(); + let traitname = Ident::new(&fnname.to_string().to_pascal_case(), fnsig.ident.span()); + + let result = quote! { + #ast + + trait #traitname { + #fnsig; + } + + impl> #traitname for &mut T { + #fnsig { + self.execute( + golem_rust::#operation( + |#input_pattern| { + #fnname(#(#input_args), *) + }, + |#compensation_pattern| { + #compensate(#compensation, (op_result,), (#(#compensation_args), *)).map_err(|err| err.0) + } + ), + (#(#input_args), *) + ) + } + } + }; + + result.into() +} + +fn result_type(ty: &Type) -> Option<(Type, Type)> { + match ty { + Type::Group(group) => result_type(&group.elem), + Type::Paren(paren) => result_type(&paren.elem), + Type::Path(type_path) => { + if type_path.qself.is_none() { + let idents = type_path + .path + .segments + .iter() + .map(|segment| segment.ident.to_string()) + .collect::>(); + if idents == vec!["Result"] + || idents == vec!["std", "result", "Result"] + || idents == vec!["core", "result", "Result"] + { + let last_segment = type_path.path.segments.last().unwrap(); + let syn::PathArguments::AngleBracketed(generics) = &last_segment.arguments + else { + return None; + }; + if generics.args.len() != 2 { + return None; + } + let syn::GenericArgument::Type(success_type) = &generics.args[0] else { + return None; + }; + let syn::GenericArgument::Type(err_type) = &generics.args[1] else { + return None; + }; + + Some((success_type.clone(), err_type.clone())) + } else { + None + } + } else { + None + } + } + _ => None, + } +} diff --git a/src/wit_gen.rs b/golem-rust-macro/src/wit_gen.rs similarity index 100% rename from src/wit_gen.rs rename to golem-rust-macro/src/wit_gen.rs diff --git a/golem-rust/Cargo.toml b/golem-rust/Cargo.toml new file mode 100644 index 0000000..23beed1 --- /dev/null +++ b/golem-rust/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "golem-rust" +version = "0.0.0" +edition = "2021" +license = "Apache-2.0" +homepage = "https://golem.cloud" +description = "Golem Rust tooling library that facilitates writing Golem backends in Rust" + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib", "rlib"] + +[dependencies] +golem-rust-macro = { path = "../golem-rust-macro", version = "0.0.0", optional = true } +uuid = { version = "1.8.0", features = ["v4"], optional = true } +wit-bindgen = { version = "0.17.0", default-features = false, features = ["realloc"] } + +[features] +default = ["macro", "uuid"] +macro = ["dep:golem-rust-macro"] +uuid = ["dep:uuid"] + +[package.metadata.component] +package = "golem:rust" + +[package.metadata.component.target] +path = "wit" + +[package.metadata.component.target.dependencies] +"golem:api" = { path = "wit/deps/golem" } +"golem:rpc" = { path = "wit/deps/wasm-rpc" } +"wasi:clocks" = { path = "wit/deps/clocks" } +"wasi:io" = { path = "wit/deps/io" } \ No newline at end of file diff --git a/golem-rust/README.md b/golem-rust/README.md new file mode 100644 index 0000000..74f9edf --- /dev/null +++ b/golem-rust/README.md @@ -0,0 +1,5 @@ +# golem-rust + +A library that help writing [Golem](https://golem.cloud) programs by providing higher level Rust +wrappers for Golem's runtime APIs, including functions for defining and performing operations +transactionally. diff --git a/golem-rust/src/bindings.rs b/golem-rust/src/bindings.rs new file mode 100644 index 0000000..9a6208a --- /dev/null +++ b/golem-rust/src/bindings.rs @@ -0,0 +1,2649 @@ +// Generated by `wit-bindgen` 0.16.0. DO NOT EDIT! +pub mod golem { + pub mod api { + + #[allow(clippy::all)] + pub mod host { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_section; + pub type Uri = super::super::super::golem::rpc::types::Uri; + pub type Duration = super::super::super::wasi::clocks::monotonic_clock::Duration; + /// An index into the persistent log storing all performed operations of a worker + pub type OplogIndex = u64; + /// UUID + #[repr(C)] + #[derive(Clone, Copy)] + pub struct Uuid { + pub high_bits: u64, + pub low_bits: u64, + } + impl ::core::fmt::Debug for Uuid { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("Uuid") + .field("high-bits", &self.high_bits) + .field("low-bits", &self.low_bits) + .finish() + } + } + /// Represents a Golem template + #[repr(C)] + #[derive(Clone, Copy)] + pub struct TemplateId { + pub uuid: Uuid, + } + impl ::core::fmt::Debug for TemplateId { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("TemplateId") + .field("uuid", &self.uuid) + .finish() + } + } + /// Represents a Golem worker + #[derive(Clone)] + pub struct WorkerId { + pub template_id: TemplateId, + pub worker_name: wit_bindgen::rt::string::String, + } + impl ::core::fmt::Debug for WorkerId { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("WorkerId") + .field("template-id", &self.template_id) + .field("worker-name", &self.worker_name) + .finish() + } + } + /// A promise ID is a value that can be passed to an external Golem API to complete that promise + /// from an arbitrary external source, while Golem workers can await for this completion. + #[derive(Clone)] + pub struct PromiseId { + pub worker_id: WorkerId, + pub oplog_idx: OplogIndex, + } + impl ::core::fmt::Debug for PromiseId { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("PromiseId") + .field("worker-id", &self.worker_id) + .field("oplog-idx", &self.oplog_idx) + .finish() + } + } + /// Configures how the executor retries failures + #[repr(C)] + #[derive(Clone, Copy)] + pub struct RetryPolicy { + /// The maximum number of retries before the worker becomes permanently failed + pub max_attempts: u32, + /// The minimum delay between retries (applied to the first retry) + pub min_delay: Duration, + /// The maximum delay between retries + pub max_delay: Duration, + /// Multiplier applied to the delay on each retry to implement exponential backoff + pub multiplier: u32, + } + impl ::core::fmt::Debug for RetryPolicy { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("RetryPolicy") + .field("max-attempts", &self.max_attempts) + .field("min-delay", &self.min_delay) + .field("max-delay", &self.max_delay) + .field("multiplier", &self.multiplier) + .finish() + } + } + /// Configurable persistence level for workers + #[derive(Clone, Copy)] + pub enum PersistenceLevel { + PersistNothing, + PersistRemoteSideEffects, + Smart, + } + impl ::core::fmt::Debug for PersistenceLevel { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + PersistenceLevel::PersistNothing => { + f.debug_tuple("PersistenceLevel::PersistNothing").finish() + } + PersistenceLevel::PersistRemoteSideEffects => f + .debug_tuple("PersistenceLevel::PersistRemoteSideEffects") + .finish(), + PersistenceLevel::Smart => { + f.debug_tuple("PersistenceLevel::Smart").finish() + } + } + } + } + #[repr(u8)] + #[derive(Clone, Copy, Eq, PartialEq)] + pub enum FilterComparator { + Equal, + NotEqual, + GreaterEqual, + Greater, + LessEqual, + Less, + } + impl ::core::fmt::Debug for FilterComparator { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + FilterComparator::Equal => { + f.debug_tuple("FilterComparator::Equal").finish() + } + FilterComparator::NotEqual => { + f.debug_tuple("FilterComparator::NotEqual").finish() + } + FilterComparator::GreaterEqual => { + f.debug_tuple("FilterComparator::GreaterEqual").finish() + } + FilterComparator::Greater => { + f.debug_tuple("FilterComparator::Greater").finish() + } + FilterComparator::LessEqual => { + f.debug_tuple("FilterComparator::LessEqual").finish() + } + FilterComparator::Less => f.debug_tuple("FilterComparator::Less").finish(), + } + } + } + + impl FilterComparator { + pub(crate) unsafe fn _lift(val: u8) -> FilterComparator { + if !cfg!(debug_assertions) { + return ::core::mem::transmute(val); + } + + match val { + 0 => FilterComparator::Equal, + 1 => FilterComparator::NotEqual, + 2 => FilterComparator::GreaterEqual, + 3 => FilterComparator::Greater, + 4 => FilterComparator::LessEqual, + 5 => FilterComparator::Less, + + _ => panic!("invalid enum discriminant"), + } + } + } + + #[repr(u8)] + #[derive(Clone, Copy, Eq, PartialEq)] + pub enum StringFilterComparator { + Equal, + NotEqual, + Like, + NotLike, + } + impl ::core::fmt::Debug for StringFilterComparator { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + StringFilterComparator::Equal => { + f.debug_tuple("StringFilterComparator::Equal").finish() + } + StringFilterComparator::NotEqual => { + f.debug_tuple("StringFilterComparator::NotEqual").finish() + } + StringFilterComparator::Like => { + f.debug_tuple("StringFilterComparator::Like").finish() + } + StringFilterComparator::NotLike => { + f.debug_tuple("StringFilterComparator::NotLike").finish() + } + } + } + } + + impl StringFilterComparator { + pub(crate) unsafe fn _lift(val: u8) -> StringFilterComparator { + if !cfg!(debug_assertions) { + return ::core::mem::transmute(val); + } + + match val { + 0 => StringFilterComparator::Equal, + 1 => StringFilterComparator::NotEqual, + 2 => StringFilterComparator::Like, + 3 => StringFilterComparator::NotLike, + + _ => panic!("invalid enum discriminant"), + } + } + } + + #[repr(u8)] + #[derive(Clone, Copy, Eq, PartialEq)] + pub enum WorkerStatus { + /// The worker is running an invoked function + Running, + /// The worker is ready to run an invoked function + Idle, + /// An invocation is active but waiting for something (sleeping, waiting for a promise) + Suspended, + /// The last invocation was interrupted but will be resumed + Interrupted, + /// The last invocation failed and a retry was scheduled + Retrying, + /// The last invocation failed and the worker can no longer be used + Failed, + /// The worker exited after a successful invocation and can no longer be invoked + Exited, + } + impl ::core::fmt::Debug for WorkerStatus { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + WorkerStatus::Running => f.debug_tuple("WorkerStatus::Running").finish(), + WorkerStatus::Idle => f.debug_tuple("WorkerStatus::Idle").finish(), + WorkerStatus::Suspended => { + f.debug_tuple("WorkerStatus::Suspended").finish() + } + WorkerStatus::Interrupted => { + f.debug_tuple("WorkerStatus::Interrupted").finish() + } + WorkerStatus::Retrying => f.debug_tuple("WorkerStatus::Retrying").finish(), + WorkerStatus::Failed => f.debug_tuple("WorkerStatus::Failed").finish(), + WorkerStatus::Exited => f.debug_tuple("WorkerStatus::Exited").finish(), + } + } + } + + impl WorkerStatus { + pub(crate) unsafe fn _lift(val: u8) -> WorkerStatus { + if !cfg!(debug_assertions) { + return ::core::mem::transmute(val); + } + + match val { + 0 => WorkerStatus::Running, + 1 => WorkerStatus::Idle, + 2 => WorkerStatus::Suspended, + 3 => WorkerStatus::Interrupted, + 4 => WorkerStatus::Retrying, + 5 => WorkerStatus::Failed, + 6 => WorkerStatus::Exited, + + _ => panic!("invalid enum discriminant"), + } + } + } + + #[derive(Clone)] + pub struct WorkerNameFilter { + pub comparator: StringFilterComparator, + pub value: wit_bindgen::rt::string::String, + } + impl ::core::fmt::Debug for WorkerNameFilter { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("WorkerNameFilter") + .field("comparator", &self.comparator) + .field("value", &self.value) + .finish() + } + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct WorkerStatusFilter { + pub comparator: FilterComparator, + pub value: WorkerStatus, + } + impl ::core::fmt::Debug for WorkerStatusFilter { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("WorkerStatusFilter") + .field("comparator", &self.comparator) + .field("value", &self.value) + .finish() + } + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct WorkerVersionFilter { + pub comparator: FilterComparator, + pub value: u64, + } + impl ::core::fmt::Debug for WorkerVersionFilter { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("WorkerVersionFilter") + .field("comparator", &self.comparator) + .field("value", &self.value) + .finish() + } + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct WorkerCreatedAtFilter { + pub comparator: FilterComparator, + pub value: u64, + } + impl ::core::fmt::Debug for WorkerCreatedAtFilter { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("WorkerCreatedAtFilter") + .field("comparator", &self.comparator) + .field("value", &self.value) + .finish() + } + } + #[derive(Clone)] + pub struct WorkerEnvFilter { + pub name: wit_bindgen::rt::string::String, + pub comparator: StringFilterComparator, + pub value: wit_bindgen::rt::string::String, + } + impl ::core::fmt::Debug for WorkerEnvFilter { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("WorkerEnvFilter") + .field("name", &self.name) + .field("comparator", &self.comparator) + .field("value", &self.value) + .finish() + } + } + #[derive(Clone)] + pub enum WorkerPropertyFilter { + Name(WorkerNameFilter), + Status(WorkerStatusFilter), + Version(WorkerVersionFilter), + CreatedAt(WorkerCreatedAtFilter), + Env(WorkerEnvFilter), + } + impl ::core::fmt::Debug for WorkerPropertyFilter { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + WorkerPropertyFilter::Name(e) => f + .debug_tuple("WorkerPropertyFilter::Name") + .field(e) + .finish(), + WorkerPropertyFilter::Status(e) => f + .debug_tuple("WorkerPropertyFilter::Status") + .field(e) + .finish(), + WorkerPropertyFilter::Version(e) => f + .debug_tuple("WorkerPropertyFilter::Version") + .field(e) + .finish(), + WorkerPropertyFilter::CreatedAt(e) => f + .debug_tuple("WorkerPropertyFilter::CreatedAt") + .field(e) + .finish(), + WorkerPropertyFilter::Env(e) => { + f.debug_tuple("WorkerPropertyFilter::Env").field(e).finish() + } + } + } + } + #[derive(Clone)] + pub struct WorkerAllFilter { + pub filters: wit_bindgen::rt::vec::Vec, + } + impl ::core::fmt::Debug for WorkerAllFilter { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("WorkerAllFilter") + .field("filters", &self.filters) + .finish() + } + } + #[derive(Clone)] + pub struct WorkerAnyFilter { + pub filters: wit_bindgen::rt::vec::Vec, + } + impl ::core::fmt::Debug for WorkerAnyFilter { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("WorkerAnyFilter") + .field("filters", &self.filters) + .finish() + } + } + #[derive(Clone)] + pub struct WorkerMetadata { + pub worker_id: WorkerId, + pub args: wit_bindgen::rt::vec::Vec, + pub env: wit_bindgen::rt::vec::Vec<( + wit_bindgen::rt::string::String, + wit_bindgen::rt::string::String, + )>, + pub status: WorkerStatus, + pub template_version: u64, + pub retry_count: u64, + } + impl ::core::fmt::Debug for WorkerMetadata { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("WorkerMetadata") + .field("worker-id", &self.worker_id) + .field("args", &self.args) + .field("env", &self.env) + .field("status", &self.status) + .field("template-version", &self.template_version) + .field("retry-count", &self.retry_count) + .finish() + } + } + + #[derive(Debug)] + #[repr(transparent)] + pub struct GetWorkers { + handle: wit_bindgen::rt::Resource, + } + + impl GetWorkers { + #[doc(hidden)] + pub unsafe fn from_handle(handle: u32) -> Self { + Self { + handle: wit_bindgen::rt::Resource::from_handle(handle), + } + } + + #[doc(hidden)] + pub fn into_handle(self) -> u32 { + wit_bindgen::rt::Resource::into_handle(self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> u32 { + wit_bindgen::rt::Resource::handle(&self.handle) + } + } + + unsafe impl wit_bindgen::rt::WasmResource for GetWorkers { + #[inline] + unsafe fn drop(_handle: u32) { + #[cfg(not(target_arch = "wasm32"))] + unreachable!(); + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "[resource-drop]get-workers"] + fn drop(_: u32); + } + + drop(_handle); + } + } + } + + impl GetWorkers { + #[allow(unused_unsafe, clippy::all)] + pub fn new( + template_id: TemplateId, + filter: Option<&WorkerAnyFilter>, + precise: bool, + ) -> Self { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + let mut cleanup_list = Vec::new(); + let TemplateId { uuid: uuid0 } = template_id; + let Uuid { + high_bits: high_bits1, + low_bits: low_bits1, + } = uuid0; + let (result14_0, result14_1, result14_2) = match filter { + Some(e) => { + let WorkerAnyFilter { filters: filters2 } = e; + let vec13 = filters2; + let len13 = vec13.len() as i32; + let layout13 = + alloc::Layout::from_size_align_unchecked(vec13.len() * 8, 4); + let result13 = if layout13.size() != 0 { + let ptr = alloc::alloc(layout13); + if ptr.is_null() { + alloc::handle_alloc_error(layout13); + } + ptr + } else { + { + ::core::ptr::null_mut() + } + }; + for (i, e) in vec13.into_iter().enumerate() { + let base = result13 as i32 + (i as i32) * 8; + { + let WorkerAllFilter { filters: filters3 } = e; + let vec12 = filters3; + let len12 = vec12.len() as i32; + let layout12 = alloc::Layout::from_size_align_unchecked( + vec12.len() * 32, + 8, + ); + let result12 = if layout12.size() != 0 { + let ptr = alloc::alloc(layout12); + if ptr.is_null() { + alloc::handle_alloc_error(layout12); + } + ptr + } else { + { + ::core::ptr::null_mut() + } + }; + for (i, e) in vec12.into_iter().enumerate() { + let base = result12 as i32 + (i as i32) * 32; + { + match e { + WorkerPropertyFilter::Name(e) => { + *((base + 0) as *mut u8) = (0i32) as u8; + let WorkerNameFilter { + comparator: comparator4, + value: value4, + } = e; + *((base + 8) as *mut u8) = + (comparator4.clone() as i32) as u8; + let vec5 = value4; + let ptr5 = vec5.as_ptr() as i32; + let len5 = vec5.len() as i32; + *((base + 16) as *mut i32) = len5; + *((base + 12) as *mut i32) = ptr5; + } + WorkerPropertyFilter::Status(e) => { + *((base + 0) as *mut u8) = (1i32) as u8; + let WorkerStatusFilter { + comparator: comparator6, + value: value6, + } = e; + *((base + 8) as *mut u8) = + (comparator6.clone() as i32) as u8; + *((base + 9) as *mut u8) = + (value6.clone() as i32) as u8; + } + WorkerPropertyFilter::Version(e) => { + *((base + 0) as *mut u8) = (2i32) as u8; + let WorkerVersionFilter { + comparator: comparator7, + value: value7, + } = e; + *((base + 8) as *mut u8) = + (comparator7.clone() as i32) as u8; + *((base + 16) as *mut i64) = + wit_bindgen::rt::as_i64(value7); + } + WorkerPropertyFilter::CreatedAt(e) => { + *((base + 0) as *mut u8) = (3i32) as u8; + let WorkerCreatedAtFilter { + comparator: comparator8, + value: value8, + } = e; + *((base + 8) as *mut u8) = + (comparator8.clone() as i32) as u8; + *((base + 16) as *mut i64) = + wit_bindgen::rt::as_i64(value8); + } + WorkerPropertyFilter::Env(e) => { + *((base + 0) as *mut u8) = (4i32) as u8; + let WorkerEnvFilter { + name: name9, + comparator: comparator9, + value: value9, + } = e; + let vec10 = name9; + let ptr10 = vec10.as_ptr() as i32; + let len10 = vec10.len() as i32; + *((base + 12) as *mut i32) = len10; + *((base + 8) as *mut i32) = ptr10; + *((base + 16) as *mut u8) = + (comparator9.clone() as i32) as u8; + let vec11 = value9; + let ptr11 = vec11.as_ptr() as i32; + let len11 = vec11.len() as i32; + *((base + 24) as *mut i32) = len11; + *((base + 20) as *mut i32) = ptr11; + } + } + } + } + *((base + 4) as *mut i32) = len12; + *((base + 0) as *mut i32) = result12 as i32; + cleanup_list.extend_from_slice(&[(result12, layout12)]); + } + } + cleanup_list.extend_from_slice(&[(result13, layout13)]); + + (1i32, result13 as i32, len13) + } + None => (0i32, 0i32, 0i32), + }; + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "[constructor]get-workers"] + fn wit_import(_: i64, _: i64, _: i32, _: i32, _: i32, _: i32) -> i32; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i64, _: i64, _: i32, _: i32, _: i32, _: i32) -> i32 { + unreachable!() + } + let ret = wit_import( + wit_bindgen::rt::as_i64(high_bits1), + wit_bindgen::rt::as_i64(low_bits1), + result14_0, + result14_1, + result14_2, + match precise { + true => 1, + false => 0, + }, + ); + for (ptr, layout) in cleanup_list { + if layout.size() != 0 { + alloc::dealloc(ptr, layout); + } + } + GetWorkers::from_handle(ret as u32) + } + } + } + impl GetWorkers { + #[allow(unused_unsafe, clippy::all)] + pub fn get_next(&self) -> Option> { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[repr(align(4))] + struct RetArea([u8; 12]); + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let ptr0 = ret_area.as_mut_ptr() as i32; + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "[method]get-workers.get-next"] + fn wit_import(_: i32, _: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32, _: i32) { + unreachable!() + } + wit_import((self).handle() as i32, ptr0); + let l1 = i32::from(*((ptr0 + 0) as *const u8)); + match l1 { + 0 => None, + 1 => { + let e = { + let l2 = *((ptr0 + 4) as *const i32); + let l3 = *((ptr0 + 8) as *const i32); + let base27 = l2; + let len27 = l3; + let mut result27 = Vec::with_capacity(len27 as usize); + for i in 0..len27 { + let base = base27 + i * 64; + let e27 = { + let l4 = *((base + 0) as *const i64); + let l5 = *((base + 8) as *const i64); + let l6 = *((base + 16) as *const i32); + let l7 = *((base + 20) as *const i32); + let len8 = l7 as usize; + let bytes8 = + Vec::from_raw_parts(l6 as *mut _, len8, len8); + let l9 = *((base + 24) as *const i32); + let l10 = *((base + 28) as *const i32); + let base14 = l9; + let len14 = l10; + let mut result14 = Vec::with_capacity(len14 as usize); + for i in 0..len14 { + let base = base14 + i * 8; + let e14 = { + let l11 = *((base + 0) as *const i32); + let l12 = *((base + 4) as *const i32); + let len13 = l12 as usize; + let bytes13 = Vec::from_raw_parts( + l11 as *mut _, + len13, + len13, + ); + + wit_bindgen::rt::string_lift(bytes13) + }; + result14.push(e14); + } + wit_bindgen::rt::dealloc( + base14, + (len14 as usize) * 8, + 4, + ); + let l15 = *((base + 32) as *const i32); + let l16 = *((base + 36) as *const i32); + let base23 = l15; + let len23 = l16; + let mut result23 = Vec::with_capacity(len23 as usize); + for i in 0..len23 { + let base = base23 + i * 16; + let e23 = { + let l17 = *((base + 0) as *const i32); + let l18 = *((base + 4) as *const i32); + let len19 = l18 as usize; + let bytes19 = Vec::from_raw_parts( + l17 as *mut _, + len19, + len19, + ); + let l20 = *((base + 8) as *const i32); + let l21 = *((base + 12) as *const i32); + let len22 = l21 as usize; + let bytes22 = Vec::from_raw_parts( + l20 as *mut _, + len22, + len22, + ); + + ( + wit_bindgen::rt::string_lift(bytes19), + wit_bindgen::rt::string_lift(bytes22), + ) + }; + result23.push(e23); + } + wit_bindgen::rt::dealloc( + base23, + (len23 as usize) * 16, + 4, + ); + let l24 = i32::from(*((base + 40) as *const u8)); + let l25 = *((base + 48) as *const i64); + let l26 = *((base + 56) as *const i64); + + WorkerMetadata { + worker_id: WorkerId { + template_id: TemplateId { + uuid: Uuid { + high_bits: l4 as u64, + low_bits: l5 as u64, + }, + }, + worker_name: wit_bindgen::rt::string_lift( + bytes8, + ), + }, + args: result14, + env: result23, + status: WorkerStatus::_lift(l24 as u8), + template_version: l25 as u64, + retry_count: l26 as u64, + } + }; + result27.push(e27); + } + wit_bindgen::rt::dealloc(base27, (len27 as usize) * 64, 8); + + result27 + }; + Some(e) + } + _ => wit_bindgen::rt::invalid_enum_discriminant(), + } + } + } + } + #[allow(unused_unsafe, clippy::all)] + /// Create a new promise + pub fn golem_create_promise() -> PromiseId { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[repr(align(8))] + struct RetArea([u8; 32]); + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let ptr0 = ret_area.as_mut_ptr() as i32; + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "golem-create-promise"] + fn wit_import(_: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32) { + unreachable!() + } + wit_import(ptr0); + let l1 = *((ptr0 + 0) as *const i64); + let l2 = *((ptr0 + 8) as *const i64); + let l3 = *((ptr0 + 16) as *const i32); + let l4 = *((ptr0 + 20) as *const i32); + let len5 = l4 as usize; + let bytes5 = Vec::from_raw_parts(l3 as *mut _, len5, len5); + let l6 = *((ptr0 + 24) as *const i64); + PromiseId { + worker_id: WorkerId { + template_id: TemplateId { + uuid: Uuid { + high_bits: l1 as u64, + low_bits: l2 as u64, + }, + }, + worker_name: wit_bindgen::rt::string_lift(bytes5), + }, + oplog_idx: l6 as u64, + } + } + } + #[allow(unused_unsafe, clippy::all)] + /// Suspends execution until the given promise gets completed, and returns the payload passed to + /// the promise completion. + pub fn golem_await_promise(promise_id: &PromiseId) -> wit_bindgen::rt::vec::Vec { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[repr(align(4))] + struct RetArea([u8; 8]); + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let PromiseId { + worker_id: worker_id0, + oplog_idx: oplog_idx0, + } = promise_id; + let WorkerId { + template_id: template_id1, + worker_name: worker_name1, + } = worker_id0; + let TemplateId { uuid: uuid2 } = template_id1; + let Uuid { + high_bits: high_bits3, + low_bits: low_bits3, + } = uuid2; + let vec4 = worker_name1; + let ptr4 = vec4.as_ptr() as i32; + let len4 = vec4.len() as i32; + let ptr5 = ret_area.as_mut_ptr() as i32; + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "golem-await-promise"] + fn wit_import(_: i64, _: i64, _: i32, _: i32, _: i64, _: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i64, _: i64, _: i32, _: i32, _: i64, _: i32) { + unreachable!() + } + wit_import( + wit_bindgen::rt::as_i64(high_bits3), + wit_bindgen::rt::as_i64(low_bits3), + ptr4, + len4, + wit_bindgen::rt::as_i64(oplog_idx0), + ptr5, + ); + let l6 = *((ptr5 + 0) as *const i32); + let l7 = *((ptr5 + 4) as *const i32); + let len8 = l7 as usize; + Vec::from_raw_parts(l6 as *mut _, len8, len8) + } + } + #[allow(unused_unsafe, clippy::all)] + /// Completes the given promise with the given payload. Returns true if the promise was completed, false + /// if the promise was already completed. The payload is passed to the worker that is awaiting the promise. + pub fn golem_complete_promise(promise_id: &PromiseId, data: &[u8]) -> bool { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + let PromiseId { + worker_id: worker_id0, + oplog_idx: oplog_idx0, + } = promise_id; + let WorkerId { + template_id: template_id1, + worker_name: worker_name1, + } = worker_id0; + let TemplateId { uuid: uuid2 } = template_id1; + let Uuid { + high_bits: high_bits3, + low_bits: low_bits3, + } = uuid2; + let vec4 = worker_name1; + let ptr4 = vec4.as_ptr() as i32; + let len4 = vec4.len() as i32; + let vec5 = data; + let ptr5 = vec5.as_ptr() as i32; + let len5 = vec5.len() as i32; + + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "golem-complete-promise"] + fn wit_import( + _: i64, + _: i64, + _: i32, + _: i32, + _: i64, + _: i32, + _: i32, + ) -> i32; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i64, _: i64, _: i32, _: i32, _: i64, _: i32, _: i32) -> i32 { + unreachable!() + } + let ret = wit_import( + wit_bindgen::rt::as_i64(high_bits3), + wit_bindgen::rt::as_i64(low_bits3), + ptr4, + len4, + wit_bindgen::rt::as_i64(oplog_idx0), + ptr5, + len5, + ); + wit_bindgen::rt::bool_lift(ret as u8) + } + } + #[allow(unused_unsafe, clippy::all)] + /// Deletes the given promise + pub fn golem_delete_promise(promise_id: &PromiseId) { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + let PromiseId { + worker_id: worker_id0, + oplog_idx: oplog_idx0, + } = promise_id; + let WorkerId { + template_id: template_id1, + worker_name: worker_name1, + } = worker_id0; + let TemplateId { uuid: uuid2 } = template_id1; + let Uuid { + high_bits: high_bits3, + low_bits: low_bits3, + } = uuid2; + let vec4 = worker_name1; + let ptr4 = vec4.as_ptr() as i32; + let len4 = vec4.len() as i32; + + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "golem-delete-promise"] + fn wit_import(_: i64, _: i64, _: i32, _: i32, _: i64); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i64, _: i64, _: i32, _: i32, _: i64) { + unreachable!() + } + wit_import( + wit_bindgen::rt::as_i64(high_bits3), + wit_bindgen::rt::as_i64(low_bits3), + ptr4, + len4, + wit_bindgen::rt::as_i64(oplog_idx0), + ); + } + } + #[allow(unused_unsafe, clippy::all)] + /// Returns a Golem worker URI that can be used to invoke a given function on the current worker + pub fn get_self_uri(function_name: &str) -> Uri { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[repr(align(4))] + struct RetArea([u8; 8]); + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let vec0 = function_name; + let ptr0 = vec0.as_ptr() as i32; + let len0 = vec0.len() as i32; + let ptr1 = ret_area.as_mut_ptr() as i32; + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "get-self-uri"] + fn wit_import(_: i32, _: i32, _: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32, _: i32, _: i32) { + unreachable!() + } + wit_import(ptr0, len0, ptr1); + let l2 = *((ptr1 + 0) as *const i32); + let l3 = *((ptr1 + 4) as *const i32); + let len4 = l3 as usize; + let bytes4 = Vec::from_raw_parts(l2 as *mut _, len4, len4); + super::super::super::golem::rpc::types::Uri { + value: wit_bindgen::rt::string_lift(bytes4), + } + } + } + #[allow(unused_unsafe, clippy::all)] + /// Returns the current position in the persistent op log + pub fn get_oplog_index() -> OplogIndex { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "get-oplog-index"] + fn wit_import() -> i64; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import() -> i64 { + unreachable!() + } + let ret = wit_import(); + ret as u64 + } + } + #[allow(unused_unsafe, clippy::all)] + /// Makes the current worker travel back in time and continue execution from the given position in the persistent + /// op log. + pub fn set_oplog_index(oplog_idx: OplogIndex) { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "set-oplog-index"] + fn wit_import(_: i64); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i64) { + unreachable!() + } + wit_import(wit_bindgen::rt::as_i64(oplog_idx)); + } + } + #[allow(unused_unsafe, clippy::all)] + /// Blocks the execution until the oplog has been written to at least the specified number of replicas, + /// or the maximum number of replicas if the requested number is higher. + pub fn oplog_commit(replicas: u8) { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "oplog-commit"] + fn wit_import(_: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32) { + unreachable!() + } + wit_import(wit_bindgen::rt::as_i32(replicas)); + } + } + #[allow(unused_unsafe, clippy::all)] + /// Marks the beginning of an atomic operation. + /// In case of a failure within the region selected by `mark-begin-operation` and `mark-end-operation` + /// the whole region will be reexecuted on retry. + /// The end of the region is when `mark-end-operation` is called with the returned oplog-index. + pub fn mark_begin_operation() -> OplogIndex { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "mark-begin-operation"] + fn wit_import() -> i64; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import() -> i64 { + unreachable!() + } + let ret = wit_import(); + ret as u64 + } + } + #[allow(unused_unsafe, clippy::all)] + /// Commits this atomic operation. After `mark-end-operation` is called for a given index, further calls + /// with the same parameter will do nothing. + pub fn mark_end_operation(begin: OplogIndex) { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "mark-end-operation"] + fn wit_import(_: i64); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i64) { + unreachable!() + } + wit_import(wit_bindgen::rt::as_i64(begin)); + } + } + #[allow(unused_unsafe, clippy::all)] + /// Gets the current retry policy associated with the worker + pub fn get_retry_policy() -> RetryPolicy { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[repr(align(8))] + struct RetArea([u8; 32]); + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let ptr0 = ret_area.as_mut_ptr() as i32; + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "get-retry-policy"] + fn wit_import(_: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32) { + unreachable!() + } + wit_import(ptr0); + let l1 = *((ptr0 + 0) as *const i32); + let l2 = *((ptr0 + 8) as *const i64); + let l3 = *((ptr0 + 16) as *const i64); + let l4 = *((ptr0 + 24) as *const i32); + RetryPolicy { + max_attempts: l1 as u32, + min_delay: l2 as u64, + max_delay: l3 as u64, + multiplier: l4 as u32, + } + } + } + #[allow(unused_unsafe, clippy::all)] + /// Overrides the current retry policy associated with the worker. Following this call, `get-retry-policy` will return the + /// new retry policy. + pub fn set_retry_policy(new_retry_policy: RetryPolicy) { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + let RetryPolicy { + max_attempts: max_attempts0, + min_delay: min_delay0, + max_delay: max_delay0, + multiplier: multiplier0, + } = new_retry_policy; + + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "set-retry-policy"] + fn wit_import(_: i32, _: i64, _: i64, _: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32, _: i64, _: i64, _: i32) { + unreachable!() + } + wit_import( + wit_bindgen::rt::as_i32(max_attempts0), + wit_bindgen::rt::as_i64(min_delay0), + wit_bindgen::rt::as_i64(max_delay0), + wit_bindgen::rt::as_i32(multiplier0), + ); + } + } + #[allow(unused_unsafe, clippy::all)] + /// Gets the worker's current persistence level. + pub fn get_oplog_persistence_level() -> PersistenceLevel { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "get-oplog-persistence-level"] + fn wit_import() -> i32; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import() -> i32 { + unreachable!() + } + let ret = wit_import(); + let v0 = match ret { + 0 => PersistenceLevel::PersistNothing, + 1 => PersistenceLevel::PersistRemoteSideEffects, + n => { + debug_assert_eq!(n, 2, "invalid enum discriminant"); + PersistenceLevel::Smart + } + }; + v0 + } + } + #[allow(unused_unsafe, clippy::all)] + /// Sets the worker's current persistence level. This can increase the performance of execution in cases where durable + /// execution is not required. + pub fn set_oplog_persistence_level(new_persistence_level: PersistenceLevel) { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + let result0 = match new_persistence_level { + PersistenceLevel::PersistNothing => 0i32, + PersistenceLevel::PersistRemoteSideEffects => 1i32, + PersistenceLevel::Smart => 2i32, + }; + + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "set-oplog-persistence-level"] + fn wit_import(_: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32) { + unreachable!() + } + wit_import(result0); + } + } + #[allow(unused_unsafe, clippy::all)] + /// Gets the current idempotence mode. See `set-idempotence-mode` for details. + pub fn get_idempotence_mode() -> bool { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "get-idempotence-mode"] + fn wit_import() -> i32; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import() -> i32 { + unreachable!() + } + let ret = wit_import(); + wit_bindgen::rt::bool_lift(ret as u8) + } + } + #[allow(unused_unsafe, clippy::all)] + /// Sets the current idempotence mode. The default is true. + /// True means side-effects are treated idempotent and Golem guarantees at-least-once semantics. + /// In case of false the executor provides at-most-once semantics, failing the worker in case it is + /// not known if the side effect was already executed. + pub fn set_idempotence_mode(idempotent: bool) { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "set-idempotence-mode"] + fn wit_import(_: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32) { + unreachable!() + } + wit_import(match idempotent { + true => 1, + false => 0, + }); + } + } + #[allow(unused_unsafe, clippy::all)] + /// Generates an idempotency key. This operation will never be replayed — + /// i.e. not only is this key generated, but it is persisted and committed, such that the key can be used in third-party systems (e.g. payment processing) + /// to introduce idempotence. + pub fn generate_idempotency_key() -> Uuid { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[repr(align(8))] + struct RetArea([u8; 16]); + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let ptr0 = ret_area.as_mut_ptr() as i32; + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:api/host@0.2.0")] + extern "C" { + #[link_name = "generate-idempotency-key"] + fn wit_import(_: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32) { + unreachable!() + } + wit_import(ptr0); + let l1 = *((ptr0 + 0) as *const i64); + let l2 = *((ptr0 + 8) as *const i64); + Uuid { + high_bits: l1 as u64, + low_bits: l2 as u64, + } + } + } + } + } + pub mod rpc { + + #[allow(clippy::all)] + pub mod types { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_section; + pub type NodeIndex = i32; + #[derive(Clone)] + pub enum WitNode { + RecordValue(wit_bindgen::rt::vec::Vec), + VariantValue((u32, Option)), + EnumValue(u32), + FlagsValue(wit_bindgen::rt::vec::Vec), + TupleValue(wit_bindgen::rt::vec::Vec), + ListValue(wit_bindgen::rt::vec::Vec), + OptionValue(Option), + ResultValue(Result, Option>), + PrimU8(u8), + PrimU16(u16), + PrimU32(u32), + PrimU64(u64), + PrimS8(i8), + PrimS16(i16), + PrimS32(i32), + PrimS64(i64), + PrimFloat32(f32), + PrimFloat64(f64), + PrimChar(char), + PrimBool(bool), + PrimString(wit_bindgen::rt::string::String), + } + impl ::core::fmt::Debug for WitNode { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + WitNode::RecordValue(e) => { + f.debug_tuple("WitNode::RecordValue").field(e).finish() + } + WitNode::VariantValue(e) => { + f.debug_tuple("WitNode::VariantValue").field(e).finish() + } + WitNode::EnumValue(e) => { + f.debug_tuple("WitNode::EnumValue").field(e).finish() + } + WitNode::FlagsValue(e) => { + f.debug_tuple("WitNode::FlagsValue").field(e).finish() + } + WitNode::TupleValue(e) => { + f.debug_tuple("WitNode::TupleValue").field(e).finish() + } + WitNode::ListValue(e) => { + f.debug_tuple("WitNode::ListValue").field(e).finish() + } + WitNode::OptionValue(e) => { + f.debug_tuple("WitNode::OptionValue").field(e).finish() + } + WitNode::ResultValue(e) => { + f.debug_tuple("WitNode::ResultValue").field(e).finish() + } + WitNode::PrimU8(e) => f.debug_tuple("WitNode::PrimU8").field(e).finish(), + WitNode::PrimU16(e) => f.debug_tuple("WitNode::PrimU16").field(e).finish(), + WitNode::PrimU32(e) => f.debug_tuple("WitNode::PrimU32").field(e).finish(), + WitNode::PrimU64(e) => f.debug_tuple("WitNode::PrimU64").field(e).finish(), + WitNode::PrimS8(e) => f.debug_tuple("WitNode::PrimS8").field(e).finish(), + WitNode::PrimS16(e) => f.debug_tuple("WitNode::PrimS16").field(e).finish(), + WitNode::PrimS32(e) => f.debug_tuple("WitNode::PrimS32").field(e).finish(), + WitNode::PrimS64(e) => f.debug_tuple("WitNode::PrimS64").field(e).finish(), + WitNode::PrimFloat32(e) => { + f.debug_tuple("WitNode::PrimFloat32").field(e).finish() + } + WitNode::PrimFloat64(e) => { + f.debug_tuple("WitNode::PrimFloat64").field(e).finish() + } + WitNode::PrimChar(e) => { + f.debug_tuple("WitNode::PrimChar").field(e).finish() + } + WitNode::PrimBool(e) => { + f.debug_tuple("WitNode::PrimBool").field(e).finish() + } + WitNode::PrimString(e) => { + f.debug_tuple("WitNode::PrimString").field(e).finish() + } + } + } + } + #[derive(Clone)] + pub struct WitValue { + pub nodes: wit_bindgen::rt::vec::Vec, + } + impl ::core::fmt::Debug for WitValue { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("WitValue") + .field("nodes", &self.nodes) + .finish() + } + } + #[derive(Clone)] + pub struct Uri { + pub value: wit_bindgen::rt::string::String, + } + impl ::core::fmt::Debug for Uri { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("Uri").field("value", &self.value).finish() + } + } + #[derive(Clone)] + pub enum RpcError { + ProtocolError(wit_bindgen::rt::string::String), + Denied(wit_bindgen::rt::string::String), + NotFound(wit_bindgen::rt::string::String), + RemoteInternalError(wit_bindgen::rt::string::String), + } + impl ::core::fmt::Debug for RpcError { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + RpcError::ProtocolError(e) => { + f.debug_tuple("RpcError::ProtocolError").field(e).finish() + } + RpcError::Denied(e) => f.debug_tuple("RpcError::Denied").field(e).finish(), + RpcError::NotFound(e) => { + f.debug_tuple("RpcError::NotFound").field(e).finish() + } + RpcError::RemoteInternalError(e) => f + .debug_tuple("RpcError::RemoteInternalError") + .field(e) + .finish(), + } + } + } + impl ::core::fmt::Display for RpcError { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + write!(f, "{:?}", self) + } + } + + impl std::error::Error for RpcError {} + + #[derive(Debug)] + #[repr(transparent)] + pub struct WasmRpc { + handle: wit_bindgen::rt::Resource, + } + + impl WasmRpc { + #[doc(hidden)] + pub unsafe fn from_handle(handle: u32) -> Self { + Self { + handle: wit_bindgen::rt::Resource::from_handle(handle), + } + } + + #[doc(hidden)] + pub fn into_handle(self) -> u32 { + wit_bindgen::rt::Resource::into_handle(self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> u32 { + wit_bindgen::rt::Resource::handle(&self.handle) + } + } + + unsafe impl wit_bindgen::rt::WasmResource for WasmRpc { + #[inline] + unsafe fn drop(_handle: u32) { + #[cfg(not(target_arch = "wasm32"))] + unreachable!(); + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "golem:rpc/types@0.1.0")] + extern "C" { + #[link_name = "[resource-drop]wasm-rpc"] + fn drop(_: u32); + } + + drop(_handle); + } + } + } + + impl WasmRpc { + #[allow(unused_unsafe, clippy::all)] + pub fn new(location: &Uri) -> Self { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + let Uri { value: value0 } = location; + let vec1 = value0; + let ptr1 = vec1.as_ptr() as i32; + let len1 = vec1.len() as i32; + + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:rpc/types@0.1.0")] + extern "C" { + #[link_name = "[constructor]wasm-rpc"] + fn wit_import(_: i32, _: i32) -> i32; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32, _: i32) -> i32 { + unreachable!() + } + let ret = wit_import(ptr1, len1); + WasmRpc::from_handle(ret as u32) + } + } + } + impl WasmRpc { + #[allow(unused_unsafe, clippy::all)] + pub fn invoke_and_await( + &self, + function_name: &str, + function_params: &[WitValue], + ) -> Result { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + let mut cleanup_list = Vec::new(); + + #[repr(align(4))] + struct RetArea([u8; 16]); + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let vec0 = function_name; + let ptr0 = vec0.as_ptr() as i32; + let len0 = vec0.len() as i32; + let vec9 = function_params; + let len9 = vec9.len() as i32; + let layout9 = alloc::Layout::from_size_align_unchecked(vec9.len() * 8, 4); + let result9 = if layout9.size() != 0 { + let ptr = alloc::alloc(layout9); + if ptr.is_null() { + alloc::handle_alloc_error(layout9); + } + ptr + } else { + { + ::core::ptr::null_mut() + } + }; + for (i, e) in vec9.into_iter().enumerate() { + let base = result9 as i32 + (i as i32) * 8; + { + let WitValue { nodes: nodes1 } = e; + let vec8 = nodes1; + let len8 = vec8.len() as i32; + let layout8 = + alloc::Layout::from_size_align_unchecked(vec8.len() * 24, 8); + let result8 = if layout8.size() != 0 { + let ptr = alloc::alloc(layout8); + if ptr.is_null() { + alloc::handle_alloc_error(layout8); + } + ptr + } else { + { + ::core::ptr::null_mut() + } + }; + for (i, e) in vec8.into_iter().enumerate() { + let base = result8 as i32 + (i as i32) * 24; + { + match e { + WitNode::RecordValue(e) => { + *((base + 0) as *mut u8) = (0i32) as u8; + let vec2 = e; + let ptr2 = vec2.as_ptr() as i32; + let len2 = vec2.len() as i32; + *((base + 12) as *mut i32) = len2; + *((base + 8) as *mut i32) = ptr2; + } + WitNode::VariantValue(e) => { + *((base + 0) as *mut u8) = (1i32) as u8; + let (t3_0, t3_1) = e; + *((base + 8) as *mut i32) = + wit_bindgen::rt::as_i32(t3_0); + match t3_1 { + Some(e) => { + *((base + 12) as *mut u8) = (1i32) as u8; + *((base + 16) as *mut i32) = + wit_bindgen::rt::as_i32(e); + } + None => { + *((base + 12) as *mut u8) = (0i32) as u8; + } + }; + } + WitNode::EnumValue(e) => { + *((base + 0) as *mut u8) = (2i32) as u8; + *((base + 8) as *mut i32) = + wit_bindgen::rt::as_i32(e); + } + WitNode::FlagsValue(e) => { + *((base + 0) as *mut u8) = (3i32) as u8; + let vec4 = e; + let len4 = vec4.len() as i32; + let layout4 = + alloc::Layout::from_size_align_unchecked( + vec4.len() * 1, + 1, + ); + let result4 = if layout4.size() != 0 { + let ptr = alloc::alloc(layout4); + if ptr.is_null() { + alloc::handle_alloc_error(layout4); + } + ptr + } else { + { + ::core::ptr::null_mut() + } + }; + for (i, e) in vec4.into_iter().enumerate() { + let base = result4 as i32 + (i as i32) * 1; + { + *((base + 0) as *mut u8) = (match e { + true => 1, + false => 0, + }) + as u8; + } + } + *((base + 12) as *mut i32) = len4; + *((base + 8) as *mut i32) = result4 as i32; + cleanup_list + .extend_from_slice(&[(result4, layout4)]); + } + WitNode::TupleValue(e) => { + *((base + 0) as *mut u8) = (4i32) as u8; + let vec5 = e; + let ptr5 = vec5.as_ptr() as i32; + let len5 = vec5.len() as i32; + *((base + 12) as *mut i32) = len5; + *((base + 8) as *mut i32) = ptr5; + } + WitNode::ListValue(e) => { + *((base + 0) as *mut u8) = (5i32) as u8; + let vec6 = e; + let ptr6 = vec6.as_ptr() as i32; + let len6 = vec6.len() as i32; + *((base + 12) as *mut i32) = len6; + *((base + 8) as *mut i32) = ptr6; + } + WitNode::OptionValue(e) => { + *((base + 0) as *mut u8) = (6i32) as u8; + match e { + Some(e) => { + *((base + 8) as *mut u8) = (1i32) as u8; + *((base + 12) as *mut i32) = + wit_bindgen::rt::as_i32(e); + } + None => { + *((base + 8) as *mut u8) = (0i32) as u8; + } + }; + } + WitNode::ResultValue(e) => { + *((base + 0) as *mut u8) = (7i32) as u8; + match e { + Ok(e) => { + *((base + 8) as *mut u8) = (0i32) as u8; + match e { + Some(e) => { + *((base + 12) as *mut u8) = + (1i32) as u8; + *((base + 16) as *mut i32) = + wit_bindgen::rt::as_i32(e); + } + None => { + *((base + 12) as *mut u8) = + (0i32) as u8; + } + }; + } + Err(e) => { + *((base + 8) as *mut u8) = (1i32) as u8; + match e { + Some(e) => { + *((base + 12) as *mut u8) = + (1i32) as u8; + *((base + 16) as *mut i32) = + wit_bindgen::rt::as_i32(e); + } + None => { + *((base + 12) as *mut u8) = + (0i32) as u8; + } + }; + } + }; + } + WitNode::PrimU8(e) => { + *((base + 0) as *mut u8) = (8i32) as u8; + *((base + 8) as *mut u8) = + (wit_bindgen::rt::as_i32(e)) as u8; + } + WitNode::PrimU16(e) => { + *((base + 0) as *mut u8) = (9i32) as u8; + *((base + 8) as *mut u16) = + (wit_bindgen::rt::as_i32(e)) as u16; + } + WitNode::PrimU32(e) => { + *((base + 0) as *mut u8) = (10i32) as u8; + *((base + 8) as *mut i32) = + wit_bindgen::rt::as_i32(e); + } + WitNode::PrimU64(e) => { + *((base + 0) as *mut u8) = (11i32) as u8; + *((base + 8) as *mut i64) = + wit_bindgen::rt::as_i64(e); + } + WitNode::PrimS8(e) => { + *((base + 0) as *mut u8) = (12i32) as u8; + *((base + 8) as *mut u8) = + (wit_bindgen::rt::as_i32(e)) as u8; + } + WitNode::PrimS16(e) => { + *((base + 0) as *mut u8) = (13i32) as u8; + *((base + 8) as *mut u16) = + (wit_bindgen::rt::as_i32(e)) as u16; + } + WitNode::PrimS32(e) => { + *((base + 0) as *mut u8) = (14i32) as u8; + *((base + 8) as *mut i32) = + wit_bindgen::rt::as_i32(e); + } + WitNode::PrimS64(e) => { + *((base + 0) as *mut u8) = (15i32) as u8; + *((base + 8) as *mut i64) = + wit_bindgen::rt::as_i64(e); + } + WitNode::PrimFloat32(e) => { + *((base + 0) as *mut u8) = (16i32) as u8; + *((base + 8) as *mut f32) = + wit_bindgen::rt::as_f32(e); + } + WitNode::PrimFloat64(e) => { + *((base + 0) as *mut u8) = (17i32) as u8; + *((base + 8) as *mut f64) = + wit_bindgen::rt::as_f64(e); + } + WitNode::PrimChar(e) => { + *((base + 0) as *mut u8) = (18i32) as u8; + *((base + 8) as *mut i32) = + wit_bindgen::rt::as_i32(e); + } + WitNode::PrimBool(e) => { + *((base + 0) as *mut u8) = (19i32) as u8; + *((base + 8) as *mut u8) = (match e { + true => 1, + false => 0, + }) + as u8; + } + WitNode::PrimString(e) => { + *((base + 0) as *mut u8) = (20i32) as u8; + let vec7 = e; + let ptr7 = vec7.as_ptr() as i32; + let len7 = vec7.len() as i32; + *((base + 12) as *mut i32) = len7; + *((base + 8) as *mut i32) = ptr7; + } + } + } + } + *((base + 4) as *mut i32) = len8; + *((base + 0) as *mut i32) = result8 as i32; + cleanup_list.extend_from_slice(&[(result8, layout8)]); + } + } + let ptr10 = ret_area.as_mut_ptr() as i32; + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "golem:rpc/types@0.1.0")] + extern "C" { + #[link_name = "[method]wasm-rpc.invoke-and-await"] + fn wit_import(_: i32, _: i32, _: i32, _: i32, _: i32, _: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32, _: i32, _: i32, _: i32, _: i32, _: i32) { + unreachable!() + } + wit_import( + (self).handle() as i32, + ptr0, + len0, + result9 as i32, + len9, + ptr10, + ); + let l11 = i32::from(*((ptr10 + 0) as *const u8)); + if layout9.size() != 0 { + alloc::dealloc(result9, layout9); + } + for (ptr, layout) in cleanup_list { + if layout.size() != 0 { + alloc::dealloc(ptr, layout); + } + } + match l11 { + 0 => { + let e = { + let l12 = *((ptr10 + 4) as *const i32); + let l13 = *((ptr10 + 8) as *const i32); + let base55 = l12; + let len55 = l13; + let mut result55 = Vec::with_capacity(len55 as usize); + for i in 0..len55 { + let base = base55 + i * 24; + let e55 = { + let l14 = i32::from(*((base + 0) as *const u8)); + let v54 = match l14 { + 0 => { + let e54 = { + let l15 = *((base + 8) as *const i32); + let l16 = *((base + 12) as *const i32); + let len17 = l16 as usize; + + Vec::from_raw_parts( + l15 as *mut _, + len17, + len17, + ) + }; + WitNode::RecordValue(e54) + } + 1 => { + let e54 = { + let l18 = *((base + 8) as *const i32); + let l19 = + i32::from(*((base + 12) as *const u8)); + + (l18 as u32, match l19 { + 0 => None, + 1 => { + let e = { + let l20 = *((base + 16) as *const i32); + + l20 + }; + Some(e) + } + _ => wit_bindgen::rt::invalid_enum_discriminant(), + }) + }; + WitNode::VariantValue(e54) + } + 2 => { + let e54 = { + let l21 = *((base + 8) as *const i32); + + l21 as u32 + }; + WitNode::EnumValue(e54) + } + 3 => { + let e54 = { + let l22 = *((base + 8) as *const i32); + let l23 = *((base + 12) as *const i32); + let base25 = l22; + let len25 = l23; + let mut result25 = + Vec::with_capacity(len25 as usize); + for i in 0..len25 { + let base = base25 + i * 1; + let e25 = { + let l24 = i32::from( + *((base + 0) as *const u8), + ); + + wit_bindgen::rt::bool_lift( + l24 as u8, + ) + }; + result25.push(e25); + } + wit_bindgen::rt::dealloc( + base25, + (len25 as usize) * 1, + 1, + ); + + result25 + }; + WitNode::FlagsValue(e54) + } + 4 => { + let e54 = { + let l26 = *((base + 8) as *const i32); + let l27 = *((base + 12) as *const i32); + let len28 = l27 as usize; + + Vec::from_raw_parts( + l26 as *mut _, + len28, + len28, + ) + }; + WitNode::TupleValue(e54) + } + 5 => { + let e54 = { + let l29 = *((base + 8) as *const i32); + let l30 = *((base + 12) as *const i32); + let len31 = l30 as usize; + + Vec::from_raw_parts( + l29 as *mut _, + len31, + len31, + ) + }; + WitNode::ListValue(e54) + } + 6 => { + let e54 = { + let l32 = + i32::from(*((base + 8) as *const u8)); + + match l32 { + 0 => None, + 1 => { + let e = { + let l33 = *((base + 12) as *const i32); + + l33 + }; + Some(e) + } + _ => wit_bindgen::rt::invalid_enum_discriminant(), + } + }; + WitNode::OptionValue(e54) + } + 7 => { + let e54 = { + let l34 = + i32::from(*((base + 8) as *const u8)); + + match l34 { + 0 => { + let e = { + let l35 = i32::from(*((base + 12) as *const u8)); + + match l35 { + 0 => None, + 1 => { + let e = { + let l36 = *((base + 16) as *const i32); + + l36 + }; + Some(e) + } + _ => wit_bindgen::rt::invalid_enum_discriminant(), + } + }; + Ok(e) + } + 1 => { + let e = { + let l37 = i32::from(*((base + 12) as *const u8)); + + match l37 { + 0 => None, + 1 => { + let e = { + let l38 = *((base + 16) as *const i32); + + l38 + }; + Some(e) + } + _ => wit_bindgen::rt::invalid_enum_discriminant(), + } + }; + Err(e) + } + _ => wit_bindgen::rt::invalid_enum_discriminant(), + } + }; + WitNode::ResultValue(e54) + } + 8 => { + let e54 = { + let l39 = + i32::from(*((base + 8) as *const u8)); + + l39 as u8 + }; + WitNode::PrimU8(e54) + } + 9 => { + let e54 = { + let l40 = + i32::from(*((base + 8) as *const u16)); + + l40 as u16 + }; + WitNode::PrimU16(e54) + } + 10 => { + let e54 = { + let l41 = *((base + 8) as *const i32); + + l41 as u32 + }; + WitNode::PrimU32(e54) + } + 11 => { + let e54 = { + let l42 = *((base + 8) as *const i64); + + l42 as u64 + }; + WitNode::PrimU64(e54) + } + 12 => { + let e54 = { + let l43 = + i32::from(*((base + 8) as *const i8)); + + l43 as i8 + }; + WitNode::PrimS8(e54) + } + 13 => { + let e54 = { + let l44 = + i32::from(*((base + 8) as *const i16)); + + l44 as i16 + }; + WitNode::PrimS16(e54) + } + 14 => { + let e54 = { + let l45 = *((base + 8) as *const i32); + + l45 + }; + WitNode::PrimS32(e54) + } + 15 => { + let e54 = { + let l46 = *((base + 8) as *const i64); + + l46 + }; + WitNode::PrimS64(e54) + } + 16 => { + let e54 = { + let l47 = *((base + 8) as *const f32); + + l47 + }; + WitNode::PrimFloat32(e54) + } + 17 => { + let e54 = { + let l48 = *((base + 8) as *const f64); + + l48 + }; + WitNode::PrimFloat64(e54) + } + 18 => { + let e54 = { + let l49 = *((base + 8) as *const i32); + + wit_bindgen::rt::char_lift(l49 as u32) + }; + WitNode::PrimChar(e54) + } + 19 => { + let e54 = { + let l50 = + i32::from(*((base + 8) as *const u8)); + + wit_bindgen::rt::bool_lift(l50 as u8) + }; + WitNode::PrimBool(e54) + } + n => { + debug_assert_eq!( + n, 20, + "invalid enum discriminant" + ); + let e54 = { + let l51 = *((base + 8) as *const i32); + let l52 = *((base + 12) as *const i32); + let len53 = l52 as usize; + let bytes53 = Vec::from_raw_parts( + l51 as *mut _, + len53, + len53, + ); + + wit_bindgen::rt::string_lift(bytes53) + }; + WitNode::PrimString(e54) + } + }; + + v54 + }; + result55.push(e55); + } + wit_bindgen::rt::dealloc(base55, (len55 as usize) * 24, 8); + + WitValue { nodes: result55 } + }; + Ok(e) + } + 1 => { + let e = { + let l56 = i32::from(*((ptr10 + 4) as *const u8)); + let v69 = match l56 { + 0 => { + let e69 = { + let l57 = *((ptr10 + 8) as *const i32); + let l58 = *((ptr10 + 12) as *const i32); + let len59 = l58 as usize; + let bytes59 = Vec::from_raw_parts( + l57 as *mut _, + len59, + len59, + ); + + wit_bindgen::rt::string_lift(bytes59) + }; + RpcError::ProtocolError(e69) + } + 1 => { + let e69 = { + let l60 = *((ptr10 + 8) as *const i32); + let l61 = *((ptr10 + 12) as *const i32); + let len62 = l61 as usize; + let bytes62 = Vec::from_raw_parts( + l60 as *mut _, + len62, + len62, + ); + + wit_bindgen::rt::string_lift(bytes62) + }; + RpcError::Denied(e69) + } + 2 => { + let e69 = { + let l63 = *((ptr10 + 8) as *const i32); + let l64 = *((ptr10 + 12) as *const i32); + let len65 = l64 as usize; + let bytes65 = Vec::from_raw_parts( + l63 as *mut _, + len65, + len65, + ); + + wit_bindgen::rt::string_lift(bytes65) + }; + RpcError::NotFound(e69) + } + n => { + debug_assert_eq!(n, 3, "invalid enum discriminant"); + let e69 = { + let l66 = *((ptr10 + 8) as *const i32); + let l67 = *((ptr10 + 12) as *const i32); + let len68 = l67 as usize; + let bytes68 = Vec::from_raw_parts( + l66 as *mut _, + len68, + len68, + ); + + wit_bindgen::rt::string_lift(bytes68) + }; + RpcError::RemoteInternalError(e69) + } + }; + + v69 + }; + Err(e) + } + _ => wit_bindgen::rt::invalid_enum_discriminant(), + } + } + } + } + } + } +} +pub mod wasi { + pub mod clocks { + + #[allow(clippy::all)] + pub mod monotonic_clock { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_section; + pub type Pollable = super::super::super::wasi::io::poll::Pollable; + /// An instant in time, in nanoseconds. An instant is relative to an + /// unspecified initial value, and can only be compared to instances from + /// the same monotonic-clock. + pub type Instant = u64; + /// A duration of time, in nanoseconds. + pub type Duration = u64; + #[allow(unused_unsafe, clippy::all)] + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + pub fn now() -> Instant { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "wasi:clocks/monotonic-clock@0.2.0")] + extern "C" { + #[link_name = "now"] + fn wit_import() -> i64; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import() -> i64 { + unreachable!() + } + let ret = wit_import(); + ret as u64 + } + } + #[allow(unused_unsafe, clippy::all)] + /// Query the resolution of the clock. Returns the duration of time + /// corresponding to a clock tick. + pub fn resolution() -> Duration { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "wasi:clocks/monotonic-clock@0.2.0")] + extern "C" { + #[link_name = "resolution"] + fn wit_import() -> i64; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import() -> i64 { + unreachable!() + } + let ret = wit_import(); + ret as u64 + } + } + #[allow(unused_unsafe, clippy::all)] + /// Create a `pollable` which will resolve once the specified instant + /// occured. + pub fn subscribe_instant(when: Instant) -> Pollable { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "wasi:clocks/monotonic-clock@0.2.0")] + extern "C" { + #[link_name = "subscribe-instant"] + fn wit_import(_: i64) -> i32; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i64) -> i32 { + unreachable!() + } + let ret = wit_import(wit_bindgen::rt::as_i64(when)); + super::super::super::wasi::io::poll::Pollable::from_handle(ret as u32) + } + } + #[allow(unused_unsafe, clippy::all)] + /// Create a `pollable` which will resolve once the given duration has + /// elapsed, starting at the time at which this function was called. + /// occured. + pub fn subscribe_duration(when: Duration) -> Pollable { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "wasi:clocks/monotonic-clock@0.2.0")] + extern "C" { + #[link_name = "subscribe-duration"] + fn wit_import(_: i64) -> i32; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i64) -> i32 { + unreachable!() + } + let ret = wit_import(wit_bindgen::rt::as_i64(when)); + super::super::super::wasi::io::poll::Pollable::from_handle(ret as u32) + } + } + } + } + pub mod io { + + #[allow(clippy::all)] + pub mod poll { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_section; + /// `pollable` epresents a single I/O event which may be ready, or not. + + #[derive(Debug)] + #[repr(transparent)] + pub struct Pollable { + handle: wit_bindgen::rt::Resource, + } + + impl Pollable { + #[doc(hidden)] + pub unsafe fn from_handle(handle: u32) -> Self { + Self { + handle: wit_bindgen::rt::Resource::from_handle(handle), + } + } + + #[doc(hidden)] + pub fn into_handle(self) -> u32 { + wit_bindgen::rt::Resource::into_handle(self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> u32 { + wit_bindgen::rt::Resource::handle(&self.handle) + } + } + + unsafe impl wit_bindgen::rt::WasmResource for Pollable { + #[inline] + unsafe fn drop(_handle: u32) { + #[cfg(not(target_arch = "wasm32"))] + unreachable!(); + + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "wasi:io/poll@0.2.0")] + extern "C" { + #[link_name = "[resource-drop]pollable"] + fn drop(_: u32); + } + + drop(_handle); + } + } + } + + impl Pollable { + #[allow(unused_unsafe, clippy::all)] + /// Return the readiness of a pollable. This function never blocks. + /// + /// Returns `true` when the pollable is ready, and `false` otherwise. + pub fn ready(&self) -> bool { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "wasi:io/poll@0.2.0")] + extern "C" { + #[link_name = "[method]pollable.ready"] + fn wit_import(_: i32) -> i32; + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32) -> i32 { + unreachable!() + } + let ret = wit_import((self).handle() as i32); + wit_bindgen::rt::bool_lift(ret as u8) + } + } + } + impl Pollable { + #[allow(unused_unsafe, clippy::all)] + /// `block` returns immediately if the pollable is ready, and otherwise + /// blocks until ready. + /// + /// This function is equivalent to calling `poll.poll` on a list + /// containing only this pollable. + pub fn block(&self) { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "wasi:io/poll@0.2.0")] + extern "C" { + #[link_name = "[method]pollable.block"] + fn wit_import(_: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32) { + unreachable!() + } + wit_import((self).handle() as i32); + } + } + } + #[allow(unused_unsafe, clippy::all)] + /// Poll for completion on a set of pollables. + /// + /// This function takes a list of pollables, which identify I/O sources of + /// interest, and waits until one or more of the events is ready for I/O. + /// + /// The result `list` contains one or more indices of handles in the + /// argument list that is ready for I/O. + /// + /// If the list contains more elements than can be indexed with a `u32` + /// value, this function traps. + /// + /// A timeout can be implemented by adding a pollable from the + /// wasi-clocks API to the list. + /// + /// This function does not return a `result`; polling in itself does not + /// do any I/O so it doesn't fail. If any of the I/O sources identified by + /// the pollables has an error, it is indicated by marking the source as + /// being reaedy for I/O. + pub fn poll(in_: &[&Pollable]) -> wit_bindgen::rt::vec::Vec { + #[allow(unused_imports)] + use wit_bindgen::rt::{alloc, string::String, vec::Vec}; + unsafe { + #[repr(align(4))] + struct RetArea([u8; 8]); + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let vec0 = in_; + let len0 = vec0.len() as i32; + let layout0 = alloc::Layout::from_size_align_unchecked(vec0.len() * 4, 4); + let result0 = if layout0.size() != 0 { + let ptr = alloc::alloc(layout0); + if ptr.is_null() { + alloc::handle_alloc_error(layout0); + } + ptr + } else { + { + ::core::ptr::null_mut() + } + }; + for (i, e) in vec0.into_iter().enumerate() { + let base = result0 as i32 + (i as i32) * 4; + { + *((base + 0) as *mut i32) = (e).handle() as i32; + } + } + let ptr1 = ret_area.as_mut_ptr() as i32; + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "wasi:io/poll@0.2.0")] + extern "C" { + #[link_name = "poll"] + fn wit_import(_: i32, _: i32, _: i32); + } + + #[cfg(not(target_arch = "wasm32"))] + fn wit_import(_: i32, _: i32, _: i32) { + unreachable!() + } + wit_import(result0 as i32, len0, ptr1); + let l2 = *((ptr1 + 0) as *const i32); + let l3 = *((ptr1 + 4) as *const i32); + let len4 = l3 as usize; + if layout0.size() != 0 { + alloc::dealloc(result0, layout0); + } + Vec::from_raw_parts(l2 as *mut _, len4, len4) + } + } + } + } +} + +#[cfg(target_arch = "wasm32")] +#[link_section = "component-type:golem-rust"] +#[doc(hidden)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 3055] = [ + 3, 0, 10, 103, 111, 108, 101, 109, 45, 114, 117, 115, 116, 0, 97, 115, 109, 13, 0, 1, 0, 7, + 235, 22, 1, 65, 2, 1, 65, 11, 1, 66, 25, 1, 122, 4, 0, 10, 110, 111, 100, 101, 45, 105, 110, + 100, 101, 120, 3, 0, 0, 1, 112, 1, 1, 107, 1, 1, 111, 2, 121, 3, 1, 112, 127, 1, 106, 1, 3, 1, + 3, 1, 113, 21, 12, 114, 101, 99, 111, 114, 100, 45, 118, 97, 108, 117, 101, 1, 2, 0, 13, 118, + 97, 114, 105, 97, 110, 116, 45, 118, 97, 108, 117, 101, 1, 4, 0, 10, 101, 110, 117, 109, 45, + 118, 97, 108, 117, 101, 1, 121, 0, 11, 102, 108, 97, 103, 115, 45, 118, 97, 108, 117, 101, 1, + 5, 0, 11, 116, 117, 112, 108, 101, 45, 118, 97, 108, 117, 101, 1, 2, 0, 10, 108, 105, 115, 116, + 45, 118, 97, 108, 117, 101, 1, 2, 0, 12, 111, 112, 116, 105, 111, 110, 45, 118, 97, 108, 117, + 101, 1, 3, 0, 12, 114, 101, 115, 117, 108, 116, 45, 118, 97, 108, 117, 101, 1, 6, 0, 7, 112, + 114, 105, 109, 45, 117, 56, 1, 125, 0, 8, 112, 114, 105, 109, 45, 117, 49, 54, 1, 123, 0, 8, + 112, 114, 105, 109, 45, 117, 51, 50, 1, 121, 0, 8, 112, 114, 105, 109, 45, 117, 54, 52, 1, 119, + 0, 7, 112, 114, 105, 109, 45, 115, 56, 1, 126, 0, 8, 112, 114, 105, 109, 45, 115, 49, 54, 1, + 124, 0, 8, 112, 114, 105, 109, 45, 115, 51, 50, 1, 122, 0, 8, 112, 114, 105, 109, 45, 115, 54, + 52, 1, 120, 0, 12, 112, 114, 105, 109, 45, 102, 108, 111, 97, 116, 51, 50, 1, 118, 0, 12, 112, + 114, 105, 109, 45, 102, 108, 111, 97, 116, 54, 52, 1, 117, 0, 9, 112, 114, 105, 109, 45, 99, + 104, 97, 114, 1, 116, 0, 9, 112, 114, 105, 109, 45, 98, 111, 111, 108, 1, 127, 0, 11, 112, 114, + 105, 109, 45, 115, 116, 114, 105, 110, 103, 1, 115, 0, 4, 0, 8, 119, 105, 116, 45, 110, 111, + 100, 101, 3, 0, 7, 1, 112, 8, 1, 114, 1, 5, 110, 111, 100, 101, 115, 9, 4, 0, 9, 119, 105, 116, + 45, 118, 97, 108, 117, 101, 3, 0, 10, 1, 114, 1, 5, 118, 97, 108, 117, 101, 115, 4, 0, 3, 117, + 114, 105, 3, 0, 12, 1, 113, 4, 14, 112, 114, 111, 116, 111, 99, 111, 108, 45, 101, 114, 114, + 111, 114, 1, 115, 0, 6, 100, 101, 110, 105, 101, 100, 1, 115, 0, 9, 110, 111, 116, 45, 102, + 111, 117, 110, 100, 1, 115, 0, 21, 114, 101, 109, 111, 116, 101, 45, 105, 110, 116, 101, 114, + 110, 97, 108, 45, 101, 114, 114, 111, 114, 1, 115, 0, 4, 0, 9, 114, 112, 99, 45, 101, 114, 114, + 111, 114, 3, 0, 14, 4, 0, 8, 119, 97, 115, 109, 45, 114, 112, 99, 3, 1, 1, 105, 16, 1, 64, 1, + 8, 108, 111, 99, 97, 116, 105, 111, 110, 13, 0, 17, 4, 0, 21, 91, 99, 111, 110, 115, 116, 114, + 117, 99, 116, 111, 114, 93, 119, 97, 115, 109, 45, 114, 112, 99, 1, 18, 1, 104, 16, 1, 112, 11, + 1, 106, 1, 11, 1, 15, 1, 64, 3, 4, 115, 101, 108, 102, 19, 13, 102, 117, 110, 99, 116, 105, + 111, 110, 45, 110, 97, 109, 101, 115, 15, 102, 117, 110, 99, 116, 105, 111, 110, 45, 112, 97, + 114, 97, 109, 115, 20, 0, 21, 4, 0, 33, 91, 109, 101, 116, 104, 111, 100, 93, 119, 97, 115, + 109, 45, 114, 112, 99, 46, 105, 110, 118, 111, 107, 101, 45, 97, 110, 100, 45, 97, 119, 97, + 105, 116, 1, 22, 3, 1, 21, 103, 111, 108, 101, 109, 58, 114, 112, 99, 47, 116, 121, 112, 101, + 115, 64, 48, 46, 49, 46, 48, 5, 0, 1, 66, 10, 4, 0, 8, 112, 111, 108, 108, 97, 98, 108, 101, 3, + 1, 1, 104, 0, 1, 64, 1, 4, 115, 101, 108, 102, 1, 0, 127, 4, 0, 22, 91, 109, 101, 116, 104, + 111, 100, 93, 112, 111, 108, 108, 97, 98, 108, 101, 46, 114, 101, 97, 100, 121, 1, 2, 1, 64, 1, + 4, 115, 101, 108, 102, 1, 1, 0, 4, 0, 22, 91, 109, 101, 116, 104, 111, 100, 93, 112, 111, 108, + 108, 97, 98, 108, 101, 46, 98, 108, 111, 99, 107, 1, 3, 1, 112, 1, 1, 112, 121, 1, 64, 1, 2, + 105, 110, 4, 0, 5, 4, 0, 4, 112, 111, 108, 108, 1, 6, 3, 1, 18, 119, 97, 115, 105, 58, 105, + 111, 47, 112, 111, 108, 108, 64, 48, 46, 50, 46, 48, 5, 1, 2, 3, 0, 1, 8, 112, 111, 108, 108, + 97, 98, 108, 101, 1, 66, 15, 2, 3, 2, 1, 2, 4, 0, 8, 112, 111, 108, 108, 97, 98, 108, 101, 3, + 0, 0, 1, 119, 4, 0, 7, 105, 110, 115, 116, 97, 110, 116, 3, 0, 2, 1, 119, 4, 0, 8, 100, 117, + 114, 97, 116, 105, 111, 110, 3, 0, 4, 1, 64, 0, 0, 3, 4, 0, 3, 110, 111, 119, 1, 6, 1, 64, 0, + 0, 5, 4, 0, 10, 114, 101, 115, 111, 108, 117, 116, 105, 111, 110, 1, 7, 1, 105, 1, 1, 64, 1, 4, + 119, 104, 101, 110, 3, 0, 8, 4, 0, 17, 115, 117, 98, 115, 99, 114, 105, 98, 101, 45, 105, 110, + 115, 116, 97, 110, 116, 1, 9, 1, 64, 1, 4, 119, 104, 101, 110, 5, 0, 8, 4, 0, 18, 115, 117, 98, + 115, 99, 114, 105, 98, 101, 45, 100, 117, 114, 97, 116, 105, 111, 110, 1, 10, 3, 1, 33, 119, + 97, 115, 105, 58, 99, 108, 111, 99, 107, 115, 47, 109, 111, 110, 111, 116, 111, 110, 105, 99, + 45, 99, 108, 111, 99, 107, 64, 48, 46, 50, 46, 48, 5, 3, 2, 3, 0, 0, 3, 117, 114, 105, 2, 3, 0, + 2, 8, 100, 117, 114, 97, 116, 105, 111, 110, 1, 66, 91, 2, 3, 2, 1, 4, 4, 0, 3, 117, 114, 105, + 3, 0, 0, 2, 3, 2, 1, 5, 4, 0, 8, 100, 117, 114, 97, 116, 105, 111, 110, 3, 0, 2, 1, 119, 4, 0, + 11, 111, 112, 108, 111, 103, 45, 105, 110, 100, 101, 120, 3, 0, 4, 1, 114, 2, 9, 104, 105, 103, + 104, 45, 98, 105, 116, 115, 119, 8, 108, 111, 119, 45, 98, 105, 116, 115, 119, 4, 0, 4, 117, + 117, 105, 100, 3, 0, 6, 1, 114, 1, 4, 117, 117, 105, 100, 7, 4, 0, 11, 116, 101, 109, 112, 108, + 97, 116, 101, 45, 105, 100, 3, 0, 8, 1, 114, 2, 11, 116, 101, 109, 112, 108, 97, 116, 101, 45, + 105, 100, 9, 11, 119, 111, 114, 107, 101, 114, 45, 110, 97, 109, 101, 115, 4, 0, 9, 119, 111, + 114, 107, 101, 114, 45, 105, 100, 3, 0, 10, 1, 114, 2, 9, 119, 111, 114, 107, 101, 114, 45, + 105, 100, 11, 9, 111, 112, 108, 111, 103, 45, 105, 100, 120, 5, 4, 0, 10, 112, 114, 111, 109, + 105, 115, 101, 45, 105, 100, 3, 0, 12, 1, 114, 4, 12, 109, 97, 120, 45, 97, 116, 116, 101, 109, + 112, 116, 115, 121, 9, 109, 105, 110, 45, 100, 101, 108, 97, 121, 3, 9, 109, 97, 120, 45, 100, + 101, 108, 97, 121, 3, 10, 109, 117, 108, 116, 105, 112, 108, 105, 101, 114, 121, 4, 0, 12, 114, + 101, 116, 114, 121, 45, 112, 111, 108, 105, 99, 121, 3, 0, 14, 1, 113, 3, 15, 112, 101, 114, + 115, 105, 115, 116, 45, 110, 111, 116, 104, 105, 110, 103, 0, 0, 27, 112, 101, 114, 115, 105, + 115, 116, 45, 114, 101, 109, 111, 116, 101, 45, 115, 105, 100, 101, 45, 101, 102, 102, 101, 99, + 116, 115, 0, 0, 5, 115, 109, 97, 114, 116, 0, 0, 4, 0, 17, 112, 101, 114, 115, 105, 115, 116, + 101, 110, 99, 101, 45, 108, 101, 118, 101, 108, 3, 0, 16, 1, 109, 6, 5, 101, 113, 117, 97, 108, + 9, 110, 111, 116, 45, 101, 113, 117, 97, 108, 13, 103, 114, 101, 97, 116, 101, 114, 45, 101, + 113, 117, 97, 108, 7, 103, 114, 101, 97, 116, 101, 114, 10, 108, 101, 115, 115, 45, 101, 113, + 117, 97, 108, 4, 108, 101, 115, 115, 4, 0, 17, 102, 105, 108, 116, 101, 114, 45, 99, 111, 109, + 112, 97, 114, 97, 116, 111, 114, 3, 0, 18, 1, 109, 4, 5, 101, 113, 117, 97, 108, 9, 110, 111, + 116, 45, 101, 113, 117, 97, 108, 4, 108, 105, 107, 101, 8, 110, 111, 116, 45, 108, 105, 107, + 101, 4, 0, 24, 115, 116, 114, 105, 110, 103, 45, 102, 105, 108, 116, 101, 114, 45, 99, 111, + 109, 112, 97, 114, 97, 116, 111, 114, 3, 0, 20, 1, 109, 7, 7, 114, 117, 110, 110, 105, 110, + 103, 4, 105, 100, 108, 101, 9, 115, 117, 115, 112, 101, 110, 100, 101, 100, 11, 105, 110, 116, + 101, 114, 114, 117, 112, 116, 101, 100, 8, 114, 101, 116, 114, 121, 105, 110, 103, 6, 102, 97, + 105, 108, 101, 100, 6, 101, 120, 105, 116, 101, 100, 4, 0, 13, 119, 111, 114, 107, 101, 114, + 45, 115, 116, 97, 116, 117, 115, 3, 0, 22, 1, 114, 2, 10, 99, 111, 109, 112, 97, 114, 97, 116, + 111, 114, 21, 5, 118, 97, 108, 117, 101, 115, 4, 0, 18, 119, 111, 114, 107, 101, 114, 45, 110, + 97, 109, 101, 45, 102, 105, 108, 116, 101, 114, 3, 0, 24, 1, 114, 2, 10, 99, 111, 109, 112, 97, + 114, 97, 116, 111, 114, 19, 5, 118, 97, 108, 117, 101, 23, 4, 0, 20, 119, 111, 114, 107, 101, + 114, 45, 115, 116, 97, 116, 117, 115, 45, 102, 105, 108, 116, 101, 114, 3, 0, 26, 1, 114, 2, + 10, 99, 111, 109, 112, 97, 114, 97, 116, 111, 114, 19, 5, 118, 97, 108, 117, 101, 119, 4, 0, + 21, 119, 111, 114, 107, 101, 114, 45, 118, 101, 114, 115, 105, 111, 110, 45, 102, 105, 108, + 116, 101, 114, 3, 0, 28, 1, 114, 2, 10, 99, 111, 109, 112, 97, 114, 97, 116, 111, 114, 19, 5, + 118, 97, 108, 117, 101, 119, 4, 0, 24, 119, 111, 114, 107, 101, 114, 45, 99, 114, 101, 97, 116, + 101, 100, 45, 97, 116, 45, 102, 105, 108, 116, 101, 114, 3, 0, 30, 1, 114, 3, 4, 110, 97, 109, + 101, 115, 10, 99, 111, 109, 112, 97, 114, 97, 116, 111, 114, 21, 5, 118, 97, 108, 117, 101, + 115, 4, 0, 17, 119, 111, 114, 107, 101, 114, 45, 101, 110, 118, 45, 102, 105, 108, 116, 101, + 114, 3, 0, 32, 1, 113, 5, 4, 110, 97, 109, 101, 1, 25, 0, 6, 115, 116, 97, 116, 117, 115, 1, + 27, 0, 7, 118, 101, 114, 115, 105, 111, 110, 1, 29, 0, 10, 99, 114, 101, 97, 116, 101, 100, 45, + 97, 116, 1, 31, 0, 3, 101, 110, 118, 1, 33, 0, 4, 0, 22, 119, 111, 114, 107, 101, 114, 45, 112, + 114, 111, 112, 101, 114, 116, 121, 45, 102, 105, 108, 116, 101, 114, 3, 0, 34, 1, 112, 35, 1, + 114, 1, 7, 102, 105, 108, 116, 101, 114, 115, 36, 4, 0, 17, 119, 111, 114, 107, 101, 114, 45, + 97, 108, 108, 45, 102, 105, 108, 116, 101, 114, 3, 0, 37, 1, 112, 38, 1, 114, 1, 7, 102, 105, + 108, 116, 101, 114, 115, 39, 4, 0, 17, 119, 111, 114, 107, 101, 114, 45, 97, 110, 121, 45, 102, + 105, 108, 116, 101, 114, 3, 0, 40, 1, 112, 115, 1, 111, 2, 115, 115, 1, 112, 43, 1, 114, 6, 9, + 119, 111, 114, 107, 101, 114, 45, 105, 100, 11, 4, 97, 114, 103, 115, 42, 3, 101, 110, 118, 44, + 6, 115, 116, 97, 116, 117, 115, 23, 16, 116, 101, 109, 112, 108, 97, 116, 101, 45, 118, 101, + 114, 115, 105, 111, 110, 119, 11, 114, 101, 116, 114, 121, 45, 99, 111, 117, 110, 116, 119, 4, + 0, 15, 119, 111, 114, 107, 101, 114, 45, 109, 101, 116, 97, 100, 97, 116, 97, 3, 0, 45, 4, 0, + 11, 103, 101, 116, 45, 119, 111, 114, 107, 101, 114, 115, 3, 1, 1, 107, 41, 1, 105, 47, 1, 64, + 3, 11, 116, 101, 109, 112, 108, 97, 116, 101, 45, 105, 100, 9, 6, 102, 105, 108, 116, 101, 114, + 48, 7, 112, 114, 101, 99, 105, 115, 101, 127, 0, 49, 4, 0, 24, 91, 99, 111, 110, 115, 116, 114, + 117, 99, 116, 111, 114, 93, 103, 101, 116, 45, 119, 111, 114, 107, 101, 114, 115, 1, 50, 1, + 104, 47, 1, 112, 46, 1, 107, 52, 1, 64, 1, 4, 115, 101, 108, 102, 51, 0, 53, 4, 0, 28, 91, 109, + 101, 116, 104, 111, 100, 93, 103, 101, 116, 45, 119, 111, 114, 107, 101, 114, 115, 46, 103, + 101, 116, 45, 110, 101, 120, 116, 1, 54, 1, 64, 0, 0, 13, 4, 0, 20, 103, 111, 108, 101, 109, + 45, 99, 114, 101, 97, 116, 101, 45, 112, 114, 111, 109, 105, 115, 101, 1, 55, 1, 112, 125, 1, + 64, 1, 10, 112, 114, 111, 109, 105, 115, 101, 45, 105, 100, 13, 0, 56, 4, 0, 19, 103, 111, 108, + 101, 109, 45, 97, 119, 97, 105, 116, 45, 112, 114, 111, 109, 105, 115, 101, 1, 57, 1, 64, 2, + 10, 112, 114, 111, 109, 105, 115, 101, 45, 105, 100, 13, 4, 100, 97, 116, 97, 56, 0, 127, 4, 0, + 22, 103, 111, 108, 101, 109, 45, 99, 111, 109, 112, 108, 101, 116, 101, 45, 112, 114, 111, 109, + 105, 115, 101, 1, 58, 1, 64, 1, 10, 112, 114, 111, 109, 105, 115, 101, 45, 105, 100, 13, 1, 0, + 4, 0, 20, 103, 111, 108, 101, 109, 45, 100, 101, 108, 101, 116, 101, 45, 112, 114, 111, 109, + 105, 115, 101, 1, 59, 1, 64, 1, 13, 102, 117, 110, 99, 116, 105, 111, 110, 45, 110, 97, 109, + 101, 115, 0, 1, 4, 0, 12, 103, 101, 116, 45, 115, 101, 108, 102, 45, 117, 114, 105, 1, 60, 1, + 64, 0, 0, 5, 4, 0, 15, 103, 101, 116, 45, 111, 112, 108, 111, 103, 45, 105, 110, 100, 101, 120, + 1, 61, 1, 64, 1, 9, 111, 112, 108, 111, 103, 45, 105, 100, 120, 5, 1, 0, 4, 0, 15, 115, 101, + 116, 45, 111, 112, 108, 111, 103, 45, 105, 110, 100, 101, 120, 1, 62, 1, 64, 1, 8, 114, 101, + 112, 108, 105, 99, 97, 115, 125, 1, 0, 4, 0, 12, 111, 112, 108, 111, 103, 45, 99, 111, 109, + 109, 105, 116, 1, 63, 4, 0, 20, 109, 97, 114, 107, 45, 98, 101, 103, 105, 110, 45, 111, 112, + 101, 114, 97, 116, 105, 111, 110, 1, 61, 1, 64, 1, 5, 98, 101, 103, 105, 110, 5, 1, 0, 4, 0, + 18, 109, 97, 114, 107, 45, 101, 110, 100, 45, 111, 112, 101, 114, 97, 116, 105, 111, 110, 1, + 64, 1, 64, 0, 0, 15, 4, 0, 16, 103, 101, 116, 45, 114, 101, 116, 114, 121, 45, 112, 111, 108, + 105, 99, 121, 1, 65, 1, 64, 1, 16, 110, 101, 119, 45, 114, 101, 116, 114, 121, 45, 112, 111, + 108, 105, 99, 121, 15, 1, 0, 4, 0, 16, 115, 101, 116, 45, 114, 101, 116, 114, 121, 45, 112, + 111, 108, 105, 99, 121, 1, 66, 1, 64, 0, 0, 17, 4, 0, 27, 103, 101, 116, 45, 111, 112, 108, + 111, 103, 45, 112, 101, 114, 115, 105, 115, 116, 101, 110, 99, 101, 45, 108, 101, 118, 101, + 108, 1, 67, 1, 64, 1, 21, 110, 101, 119, 45, 112, 101, 114, 115, 105, 115, 116, 101, 110, 99, + 101, 45, 108, 101, 118, 101, 108, 17, 1, 0, 4, 0, 27, 115, 101, 116, 45, 111, 112, 108, 111, + 103, 45, 112, 101, 114, 115, 105, 115, 116, 101, 110, 99, 101, 45, 108, 101, 118, 101, 108, 1, + 68, 1, 64, 0, 0, 127, 4, 0, 20, 103, 101, 116, 45, 105, 100, 101, 109, 112, 111, 116, 101, 110, + 99, 101, 45, 109, 111, 100, 101, 1, 69, 1, 64, 1, 10, 105, 100, 101, 109, 112, 111, 116, 101, + 110, 116, 127, 1, 0, 4, 0, 20, 115, 101, 116, 45, 105, 100, 101, 109, 112, 111, 116, 101, 110, + 99, 101, 45, 109, 111, 100, 101, 1, 70, 1, 64, 0, 0, 7, 4, 0, 24, 103, 101, 110, 101, 114, 97, + 116, 101, 45, 105, 100, 101, 109, 112, 111, 116, 101, 110, 99, 121, 45, 107, 101, 121, 1, 71, + 3, 1, 20, 103, 111, 108, 101, 109, 58, 97, 112, 105, 47, 104, 111, 115, 116, 64, 48, 46, 50, + 46, 48, 5, 6, 4, 1, 21, 103, 111, 108, 101, 109, 58, 114, 117, 115, 116, 47, 103, 111, 108, + 101, 109, 45, 114, 117, 115, 116, 4, 0, 11, 16, 1, 0, 10, 103, 111, 108, 101, 109, 45, 114, + 117, 115, 116, 3, 0, 0, 0, 16, 12, 112, 97, 99, 107, 97, 103, 101, 45, 100, 111, 99, 115, 0, + 123, 125, 0, 70, 9, 112, 114, 111, 100, 117, 99, 101, 114, 115, 1, 12, 112, 114, 111, 99, 101, + 115, 115, 101, 100, 45, 98, 121, 2, 13, 119, 105, 116, 45, 99, 111, 109, 112, 111, 110, 101, + 110, 116, 6, 48, 46, 49, 56, 46, 50, 16, 119, 105, 116, 45, 98, 105, 110, 100, 103, 101, 110, + 45, 114, 117, 115, 116, 6, 48, 46, 49, 54, 46, 48, +]; + +#[inline(never)] +#[doc(hidden)] +#[cfg(target_arch = "wasm32")] +pub fn __link_section() {} diff --git a/golem-rust/src/lib.rs b/golem-rust/src/lib.rs new file mode 100644 index 0000000..90a5763 --- /dev/null +++ b/golem-rust/src/lib.rs @@ -0,0 +1,142 @@ +// Copyright 2024 Golem Cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[allow(unused)] +#[rustfmt::skip] +mod bindings; + +#[cfg(feature = "uuid")] +mod uuid; + +mod transaction; + +use bindings::golem::api::host::*; + +pub use bindings::golem::api::host::oplog_commit; +pub use bindings::golem::api::host::RetryPolicy; + +pub use transaction::*; + +pub struct PersistenceLevelGuard { + original_level: PersistenceLevel, +} + +impl Drop for PersistenceLevelGuard { + fn drop(&mut self) { + set_oplog_persistence_level(self.original_level); + } +} + +/// Temporarily sets the oplog persistence level to the given value. +/// +/// When the returned guard is dropped, the original persistence level is restored. +#[must_use] +pub fn use_persistence_level(level: PersistenceLevel) -> PersistenceLevelGuard { + let original_level = get_oplog_persistence_level(); + set_oplog_persistence_level(level); + PersistenceLevelGuard { original_level } +} + +/// Executes the given function with the oplog persistence level set to the given value. +pub fn with_persistence_level(level: PersistenceLevel, f: impl FnOnce() -> R) -> R { + let _guard = use_persistence_level(level); + f() +} + +pub struct IdempotenceModeGuard { + original: bool, +} + +impl Drop for IdempotenceModeGuard { + fn drop(&mut self) { + set_idempotence_mode(self.original); + } +} + +/// Temporarily sets the idempotence mode to the given value. +/// +/// When the returned guard is dropped, the original idempotence mode is restored. +#[must_use] +pub fn use_idempotence_mode(mode: bool) -> IdempotenceModeGuard { + let original = get_idempotence_mode(); + set_idempotence_mode(mode); + IdempotenceModeGuard { original } +} + +/// Executes the given function with the idempotence mode set to the given value. +pub fn with_idempotence_mode(mode: bool, f: impl FnOnce() -> R) -> R { + let _guard = use_idempotence_mode(mode); + f() +} + +/// Generates an idempotency key. This operation will never be replayed — +/// i.e. not only is this key generated, but it is persisted and committed, such that the key can be used in third-party systems (e.g. payment processing) +/// to introduce idempotence. +pub fn generate_idempotency_key() -> Uuid { + bindings::golem::api::host::generate_idempotency_key() +} + +pub struct RetryPolicyGuard { + original: RetryPolicy, +} + +impl Drop for RetryPolicyGuard { + fn drop(&mut self) { + set_retry_policy(self.original); + } +} + +/// Temporarily sets the retry policy to the given value. +/// +/// When the returned guard is dropped, the original retry policy is restored. +#[must_use] +pub fn use_retry_policy(policy: RetryPolicy) -> RetryPolicyGuard { + let original = get_retry_policy(); + set_retry_policy(policy); + RetryPolicyGuard { original } +} + +/// Executes the given function with the retry policy set to the given value. +pub fn with_retry_policy(policy: RetryPolicy, f: impl FnOnce() -> R) -> R { + let _guard = use_retry_policy(policy); + f() +} + +pub struct AtomicOperationGuard { + begin: OplogIndex, +} + +impl Drop for AtomicOperationGuard { + fn drop(&mut self) { + mark_end_operation(self.begin); + } +} + +/// Marks a block as an atomic operation +/// +/// When the returned guard is dropped, the operation gets committed. +/// In case of a failure, the whole operation will be reexecuted during retry. +#[must_use] +pub fn mark_atomic_operation() -> AtomicOperationGuard { + let begin = mark_begin_operation(); + AtomicOperationGuard { begin } +} + +/// Executes the given function as an atomic operation. +/// +/// In case of a failure, the whole operation will be reexecuted during retry. +pub fn atomically(f: impl FnOnce() -> T) -> T { + let _guard = mark_atomic_operation(); + f() +} diff --git a/golem-rust/src/transaction/compfn.rs b/golem-rust/src/transaction/compfn.rs new file mode 100644 index 0000000..3889625 --- /dev/null +++ b/golem-rust/src/transaction/compfn.rs @@ -0,0 +1,115 @@ +// Copyright 2024 Golem Cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub fn call_compensation_function( + f: impl CompensationFunction, + result: impl TupleOrUnit, + input: impl TupleOrUnit, +) -> Result<(), Err> { + f.call(result, input) +} + +pub trait TupleOrUnit { + fn into(self) -> T; +} + +pub trait CompensationFunction { + fn call(self, result: impl TupleOrUnit, input: impl TupleOrUnit) -> Result<(), Err>; +} + +impl CompensationFunction<(), (), (Err,)> for F +where + F: FnOnce() -> Result<(), Err>, +{ + fn call( + self, + _result: impl TupleOrUnit<()>, + _input: impl TupleOrUnit<()>, + ) -> Result<(), (Err,)> { + self().map_err(|e| (e,))?; + Ok(()) + } +} + +impl CompensationFunction<(), (Out,), (Err,)> for F +where + F: FnOnce(Out) -> Result<(), Err>, +{ + fn call( + self, + out: impl TupleOrUnit<(Out,)>, + _input: impl TupleOrUnit<()>, + ) -> Result<(), (Err,)> { + let (out,) = out.into(); + self(out).map_err(|err| (err,)) + } +} + +impl TupleOrUnit<()> for T { + fn into(self) {} +} + +macro_rules! compensation_function { + ($($ty:ident),*) => { + impl CompensationFunction<($($ty),*,), (Out,), (Err,)> for F + where + F: FnOnce(Out, $($ty),*) -> Result<(), Err>, + { + fn call( + self, + out: impl TupleOrUnit<(Out,)>, + input: impl TupleOrUnit<($($ty),*,)>, + ) -> Result<(), (Err,)> { + #[allow(non_snake_case)] + let ( $($ty,)+ ) = input.into(); + let (out,) = out.into(); + self(out, $($ty),*).map_err(|err| (err,)) + } + } + } +} + +macro_rules! tuple_or_unit { + ($($ty:ident),*) => { + impl<$($ty),*> TupleOrUnit<($($ty,)*)> for ($($ty,)*) { + fn into(self) -> ($($ty,)*) { + self + } + } + } +} + +macro_rules! generate_for_tuples { + ($name:ident) => { + $name!(T1); + $name!(T1, T2); + $name!(T1, T2, T3); + $name!(T1, T2, T3, T4); + $name!(T1, T2, T3, T4, T5); + $name!(T1, T2, T3, T4, T5, T6); + $name!(T1, T2, T3, T4, T5, T6, T7); + $name!(T1, T2, T3, T4, T5, T6, T7, T8); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16); + }; +} + +generate_for_tuples!(tuple_or_unit); +generate_for_tuples!(compensation_function); diff --git a/golem-rust/src/transaction/mod.rs b/golem-rust/src/transaction/mod.rs new file mode 100644 index 0000000..20a5ae3 --- /dev/null +++ b/golem-rust/src/transaction/mod.rs @@ -0,0 +1,509 @@ +// Copyright 2024 Golem Cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod compfn; + +use std::fmt::{Debug, Display, Formatter}; +use std::rc::Rc; + +use crate::bindings::golem::api::host::{get_oplog_index, set_oplog_index, OplogIndex}; +use crate::mark_atomic_operation; + +pub use compfn::*; + +/// Represents an atomic operation of the transaction which has a rollback action. +/// +/// Implement this trait and use it within a `transaction` block. +/// Operations can also be constructed from closures using `operation`. +pub trait Operation: Clone { + type In: Clone; + type Out: Clone; + type Err: Clone; + + /// Executes the operation which may fail with a domain error + fn execute(&self, input: Self::In) -> Result; + + /// Executes a compensation action for the operation. + fn compensate(&self, input: Self::In, result: Self::Out) -> Result<(), Self::Err>; +} + +/// Constructs an `Operation` from two closures: one for executing the operation, +/// and one for rolling it back. The rollback operation always sees the input and +/// the output of the operation. +/// +/// This operation can run the compensation in both fallible and infallible transactions. +pub fn operation( + execute_fn: impl Fn(In) -> Result + 'static, + compensate_fn: impl Fn(In, Out) -> Result<(), Err> + 'static, +) -> impl Operation { + FnOperation { + execute_fn: Rc::new(execute_fn), + compensate_fn: Rc::new(compensate_fn), + } +} + +#[allow(clippy::type_complexity)] +struct FnOperation { + execute_fn: Rc Result>, + compensate_fn: Rc Result<(), Err>>, +} + +impl Clone for FnOperation { + fn clone(&self) -> Self { + Self { + execute_fn: self.execute_fn.clone(), + compensate_fn: self.compensate_fn.clone(), + } + } +} + +impl Operation for FnOperation { + type In = In; + type Out = Out; + type Err = Err; + + fn execute(&self, input: In) -> Result { + (self.execute_fn)(input) + } + + fn compensate(&self, input: In, result: Out) -> Result<(), Err> { + (self.compensate_fn)(input, result) + } +} + +/// The result of a transaction execution. +pub type TransactionResult = Result>; + +/// The result of a transaction execution that failed. +#[derive(Debug)] +pub enum TransactionFailure { + /// One of the operations failed with an error, and the transaction was fully rolled back. + FailedAndRolledBackCompletely(Err), + /// One of the operations failed with an error, and the transaction was partially rolled back + /// because the compensation action of one of the operations also failed. + FailedAndRolledBackPartially { + failure: Err, + compensation_failure: Err, + }, +} + +impl Display for TransactionFailure { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TransactionFailure::FailedAndRolledBackCompletely(err) => { + write!(f, "Transaction failed with {err} and rolled back completely.") + } + TransactionFailure::FailedAndRolledBackPartially { + failure, + compensation_failure, + } => write!( + f, + "Transaction failed with {failure} and rolled back partially; compensation failed with: {compensation_failure}." + ), + } + } +} + +/// Fallible transaction execution. If any operation fails, all the already executed +/// successful operation's compensation actions are executed in reverse order and the transaction +/// returns with a failure. +pub fn fallible_transaction( + f: impl FnOnce(&mut FallibleTransaction) -> Result, +) -> TransactionResult { + let mut transaction = FallibleTransaction::new(); + match f(&mut transaction) { + Ok(output) => Ok(output), + Err(error) => Err(transaction.on_fail(error)), + } +} + +/// Retry the transaction in case of failure. If any operation returns with a failure, all +/// the already executed successful operation's compensation actions are executed in reverse order +/// and the transaction gets retried, using Golem's active retry policy. +pub fn infallible_transaction(f: impl FnOnce(&mut InfallibleTransaction) -> Out) -> Out { + let oplog_index = get_oplog_index(); + let _atomic_region = mark_atomic_operation(); + let mut transaction = InfallibleTransaction::new(oplog_index); + f(&mut transaction) +} + +/// Same as `infallible_transaction`, but with strong rollback guarantees. The compensation actions +/// are guaranteed to be always executed before the transaction gets retried, even if it +/// fails due to a panic or an external executor failure. +pub fn infallible_transaction_with_strong_rollback_guarantees( + _f: impl FnOnce(&mut InfallibleTransaction) -> Out, +) -> Out { + unimplemented!() +} + +/// A generic interface for defining transactions, where the transaction mode is +/// determined by the function's parameter (it can be `FallibleTransaction` or `InfallibleTransaction`). +/// +/// This makes switching between different transaction guarantees easier, but is more constrained +/// than using the specific transaction functions where for retried transactions errors does +/// not have to be handled. +pub fn transaction(f: F) -> TransactionResult +where + T: Transaction, + F: FnOnce(&mut T) -> Result, +{ + T::run(f) +} + +/// Helper struct for coupling compensation action and the result of the operation. +#[allow(clippy::type_complexity)] +struct CompensationAction { + action: Box Result<(), Err>>, +} + +impl CompensationAction { + pub fn execute(&self) -> Result<(), Err> { + (self.action)() + } +} + +/// FallibleTransaction is a sequence of operations that are executed in a way that if any of the +/// operations fails all the already performed operation's compensation actions got executed in +/// reverse order. +/// +/// In case of fatal errors (panic) and external executor failures it does not perform the +/// compensation actions and the whole transaction gets retried. +pub struct FallibleTransaction { + compensations: Vec>, +} + +impl FallibleTransaction { + fn new() -> Self { + Self { + compensations: Vec::new(), + } + } + + pub fn execute( + &mut self, + operation: impl Operation + 'static, + input: OpIn, + ) -> Result { + let result = operation.execute(input.clone()); + if let Ok(output) = &result { + let cloned_op = operation.clone(); + let cloned_out = output.clone(); + self.compensations.push(CompensationAction { + action: Box::new(move || cloned_op.compensate(input.clone(), cloned_out.clone())), + }); + } + result + } + + fn on_fail(&mut self, failure: Err) -> TransactionFailure { + for compensation_action in self.compensations.drain(..).rev() { + if let Err(compensation_failure) = compensation_action.execute() { + return TransactionFailure::FailedAndRolledBackPartially { + failure, + compensation_failure, + }; + } + } + TransactionFailure::FailedAndRolledBackCompletely(failure) + } +} + +/// InfallibleTransaction is a sequence of operations that are executed in a way that if any of the +/// operations or the underlying Golem executor fails, the whole transaction is going to +/// be retried. +/// +/// In addition to that, **user level failures** (represented by the `Result::Err` value +/// of an operation) lead to performing the compensation actions of each already performed operation +/// in reverse order. +/// +/// Fatal errors (panic) and external executor failures are currently cannot perform the +/// rollback actions. +pub struct InfallibleTransaction { + begin_oplog_index: OplogIndex, + compensations: Vec>, +} + +impl InfallibleTransaction { + fn new(begin_oplog_index: OplogIndex) -> Self { + Self { + begin_oplog_index, + compensations: Vec::new(), + } + } + + pub fn execute< + OpIn: Clone + 'static, + OpOut: Clone + 'static, + OpErr: Debug + Clone + 'static, + >( + &mut self, + operation: impl Operation + 'static, + input: OpIn, + ) -> OpOut { + match operation.execute(input.clone()) { + Ok(output) => { + let cloned_op = operation.clone(); + let cloned_out = output.clone(); + self.compensations.push(CompensationAction { + action: Box::new(move || { + cloned_op + .compensate(input.clone(), cloned_out.clone()) + .expect("Compensation action failed"); + Ok(()) + }), + }); + output + } + Err(_) => { + self.retry(); + unreachable!() + } + } + } + + /// Stop executing the transaction and retry from the beginning, after executing the compensation actions + pub fn retry(&mut self) { + for compensation_action in self.compensations.drain(..).rev() { + let _ = compensation_action.execute(); + } + set_oplog_index(self.begin_oplog_index); + } +} + +/// A unified interface for the different types of transactions. Using it can make the code +/// easier to switch between different transactional guarantees but is more constrained in +/// terms of error types. +pub trait Transaction { + fn execute( + &mut self, + operation: impl Operation + 'static, + input: OpIn, + ) -> Result; + + fn fail(&mut self, error: Err) -> Result<(), Err>; + + fn run(f: impl FnOnce(&mut Self) -> Result) -> TransactionResult; +} + +impl Transaction for FallibleTransaction { + fn execute( + &mut self, + operation: impl Operation + 'static, + input: OpIn, + ) -> Result { + FallibleTransaction::execute(self, operation, input) + } + + fn fail(&mut self, error: Err) -> Result<(), Err> { + Err(error) + } + + fn run(f: impl FnOnce(&mut Self) -> Result) -> TransactionResult { + fallible_transaction(f) + } +} + +impl Transaction for InfallibleTransaction { + fn execute( + &mut self, + operation: impl Operation + 'static, + input: OpIn, + ) -> Result { + Ok(InfallibleTransaction::execute(self, operation, input)) + } + + fn fail(&mut self, error: Err) -> Result<(), Err> { + InfallibleTransaction::retry(self); + Err(error) + } + + fn run(f: impl FnOnce(&mut Self) -> Result) -> TransactionResult { + Ok(infallible_transaction(|tx| f(tx).unwrap())) + } +} + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + use std::rc::Rc; + + use crate::{fallible_transaction, infallible_transaction, operation}; + + // Not a real test, just verifying that the code compiles + #[test] + #[ignore] + fn tx_test_1() { + let log = Rc::new(RefCell::new(Vec::new())); + + let log1 = log.clone(); + let log2 = log.clone(); + let log3 = log.clone(); + let log4 = log.clone(); + + let op1 = operation( + move |input: String| { + log1.borrow_mut().push(format!("op1 execute {input}")); + Ok(()) + }, + move |input: String, _| { + log2.borrow_mut().push(format!("op1 rollback {input}")); + Ok(()) + }, + ); + + let op2 = operation( + move |_: ()| { + log3.clone().borrow_mut().push("op2 execute".to_string()); + Err::<(), &str>("op2 error") + }, + move |_: (), _| { + log4.clone().borrow_mut().push("op2 rollback".to_string()); + Ok(()) + }, + ); + + let result = fallible_transaction(|tx| { + println!("First we execute op1"); + tx.execute(op1, "hello".to_string())?; + println!("Then execute op2"); + tx.execute(op2, ())?; + println!("Finally compute a result"); + Ok(11) + }); + + println!("{log:?}"); + println!("{result:?}"); + } + + // Not a real test, just verifying that the code compiles + #[test] + #[ignore] + fn tx_test_2() { + let log = Rc::new(RefCell::new(Vec::new())); + + let log1 = log.clone(); + let log2 = log.clone(); + let log3 = log.clone(); + let log4 = log.clone(); + + let op1 = operation( + move |input: String| { + log1.borrow_mut().push(format!("op1 execute {input}")); + Ok::<(), ()>(()) + }, + move |input: String, _| { + log2.borrow_mut().push(format!("op1 rollback {input}")); + Ok(()) + }, + ); + + let op2 = operation( + move |_: ()| { + log3.clone().borrow_mut().push("op2 execute".to_string()); + Err::<(), &str>("op2 error") + }, + move |_: (), r| { + log4.clone() + .borrow_mut() + .push(format!("op2 rollback {r:?}")); + Ok(()) + }, + ); + + let result = infallible_transaction(|tx| { + println!("First we execute op1"); + tx.execute(op1, "hello".to_string()); + println!("Then execute op2"); + tx.execute(op2, ()); + println!("Finally compute a result"); + 11 + }); + + println!("{log:?}"); + println!("{result:?}"); + } +} + +#[cfg(test)] +#[cfg(feature = "macro")] +mod macro_tests { + use golem_rust_macro::golem_operation; + + use crate::{fallible_transaction, infallible_transaction}; + + mod golem_rust { + pub use crate::*; + } + + #[golem_operation(compensation=test_compensation)] + fn test_operation(input1: u64, input2: f32) -> Result { + println!("Op input: {input1}, {input2}"); + Ok(true) + } + + fn test_compensation(_: bool, input1: u64, input2: f32) -> Result<(), String> { + println!("Compensation input: {input1}, {input2}"); + Ok(()) + } + + #[golem_operation(compensation=test_compensation_2)] + fn test_operation_2(input1: u64, input2: f32) -> Result { + println!("Op input: {input1}, {input2}"); + Ok(true) + } + + fn test_compensation_2(result: bool) -> Result<(), String> { + println!("Compensation for operation result {result:?}"); + Ok(()) + } + + #[golem_operation(compensation=test_compensation_3)] + fn test_operation_3(input: String) -> Result<(), String> { + println!("Op input: {input}"); + Ok(()) + } + + fn test_compensation_3() -> Result<(), String> { + println!("Compensation for operation, not using any input"); + Ok(()) + } + + // Not a real test, just verifying that the code compiles + #[test] + #[ignore] + fn tx_test_1() { + let result = fallible_transaction(|tx| { + println!("Executing the annotated function as an operation directly"); + tx.test_operation(1, 0.1)?; + tx.test_operation_2(1, 0.1)?; + + Ok(11) + }); + + println!("{result:?}"); + } + + // Not a real test, just verifying that the code compiles + #[test] + #[ignore] + fn tx_test_2() { + let result = infallible_transaction(|tx| { + println!("Executing the annotated function as an operation directly"); + let _ = tx.test_operation(1, 0.1); + 11 + }); + + println!("{result:?}"); + } +} diff --git a/golem-rust/src/uuid.rs b/golem-rust/src/uuid.rs new file mode 100644 index 0000000..a98226a --- /dev/null +++ b/golem-rust/src/uuid.rs @@ -0,0 +1,31 @@ +// Copyright 2024 Golem Cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use uuid::Uuid; + +impl From for Uuid { + fn from(uuid: crate::bindings::golem::api::host::Uuid) -> Self { + Uuid::from_u64_pair(uuid.high_bits, uuid.low_bits) + } +} + +impl From for crate::bindings::golem::api::host::Uuid { + fn from(value: Uuid) -> Self { + let (high_bits, low_bits) = value.as_u64_pair(); + Self { + high_bits, + low_bits, + } + } +} diff --git a/golem-rust/wit/deps/blobstore/blobstore.wit b/golem-rust/wit/deps/blobstore/blobstore.wit new file mode 100644 index 0000000..cc52516 --- /dev/null +++ b/golem-rust/wit/deps/blobstore/blobstore.wit @@ -0,0 +1,29 @@ +package wasi:blobstore; + +// wasi-cloud Blobstore service definition +interface blobstore { + use container.{container}; + use types.{error, container-name, object-id}; + + // creates a new empty container + create-container: func(name: container-name) -> result; + + // retrieves a container by name + get-container: func(name: container-name) -> result; + + // deletes a container and all objects within it + delete-container: func(name: container-name) -> result<_, error>; + + // returns true if the container exists + container-exists: func(name: container-name) -> result; + + // copies (duplicates) an object, to the same or a different container. + // returns an error if the target container does not exist. + // overwrites destination object if it already existed. + copy-object: func(src: object-id, dest: object-id) -> result<_, error>; + + // moves or renames an object, to the same or a different container + // returns an error if the destination container does not exist. + // overwrites destination object if it already existed. + move-object: func(src:object-id, dest: object-id) -> result<_, error>; +} \ No newline at end of file diff --git a/golem-rust/wit/deps/blobstore/container.wit b/golem-rust/wit/deps/blobstore/container.wit new file mode 100644 index 0000000..3b9e8d5 --- /dev/null +++ b/golem-rust/wit/deps/blobstore/container.wit @@ -0,0 +1,68 @@ +package wasi:blobstore; + +// a Container is a collection of objects +interface container { + use wasi:io/streams@0.2.0.{ + input-stream, + output-stream, + }; + + use types.{ + container-metadata, + error, + incoming-value, + object-metadata, + object-name, + outgoing-value, + }; + + // this defines the `container` resource + resource container { + // returns container name + name: func() -> result; + + // returns container metadata + info: func() -> result; + + // retrieves an object or portion of an object, as a resource. + // Start and end offsets are inclusive. + // Once a data-blob resource has been created, the underlying bytes are held by the blobstore service for the lifetime + // of the data-blob resource, even if the object they came from is later deleted. + get-data: func(name: object-name, start: u64, end: u64) -> result; + + // creates or replaces an object with the data blob. + write-data: func(name: object-name, data: borrow) -> result<_, error>; + + // returns list of objects in the container. Order is undefined. + list-objects: func() -> result; + + // deletes object. + // does not return error if object did not exist. + delete-object: func(name: object-name) -> result<_, error>; + + // deletes multiple objects in the container + delete-objects: func(names: list) -> result<_, error>; + + // returns true if the object exists in this container + has-object: func(name: object-name) -> result; + + // returns metadata for the object + object-info: func(name: object-name) -> result; + + // removes all objects within the container, leaving the container empty. + clear: func() -> result<_, error>; + } + + // this defines the `stream-object-names` resource which is a representation of stream + resource stream-object-names { + // reads the next number of objects from the stream + // + // This function returns the list of objects read, and a boolean indicating if the end of the stream was reached. + read-stream-object-names: func(len: u64) -> result, bool>, error>; + + // skip the next number of objects in the stream + // + // This function returns the number of objects skipped, and a boolean indicating if the end of the stream was reached. + skip-stream-object-names: func(num: u64) -> result, error>; + } +} \ No newline at end of file diff --git a/golem-rust/wit/deps/blobstore/types.wit b/golem-rust/wit/deps/blobstore/types.wit new file mode 100644 index 0000000..b90f74d --- /dev/null +++ b/golem-rust/wit/deps/blobstore/types.wit @@ -0,0 +1,77 @@ +package wasi:blobstore; + +// Types used by blobstore +interface types { + use wasi:io/streams@0.2.0.{input-stream, output-stream}; + + // name of a container, a collection of objects. + // The container name may be any valid UTF-8 string. + type container-name = string; + + // name of an object within a container + // The object name may be any valid UTF-8 string. + type object-name = string; + + // TODO: define timestamp to include seconds since + // Unix epoch and nanoseconds + // https://github.com/WebAssembly/wasi-blob-store/issues/7 + type timestamp = u64; + + // size of an object, in bytes + type object-size = u64; + + type error = string; + + // information about a container + record container-metadata { + // the container's name + name: container-name, + // date and time container was created + created-at: timestamp, + } + + // information about an object + record object-metadata { + // the object's name + name: object-name, + // the object's parent container + container: container-name, + // date and time the object was created + created-at: timestamp, + // size of the object, in bytes + size: object-size, + } + + // identifier for an object that includes its container name + record object-id { + container: container-name, + object: object-name + } + + /// A data is the data stored in a data blob. The value can be of any type + /// that can be represented in a byte array. It provides a way to write the value + /// to the output-stream defined in the `wasi-io` interface. + // Soon: switch to `resource value { ... }` + resource outgoing-value { + new-outgoing-value: static func() -> outgoing-value; + outgoing-value-write-body: func() -> result; + } + + /// A incoming-value is a wrapper around a value. It provides a way to read the value + /// from the input-stream defined in the `wasi-io` interface. + /// + /// The incoming-value provides two ways to consume the value: + /// 1. `incoming-value-consume-sync` consumes the value synchronously and returns the + /// value as a list of bytes. + /// 2. `incoming-value-consume-async` consumes the value asynchronously and returns the + /// value as an input-stream. + // Soon: switch to `resource incoming-value { ... }` + resource incoming-value { + incoming-value-consume-sync: func() -> result; + incoming-value-consume-async: func() -> result; + size: func() -> u64; + } + + type incoming-value-async-body = input-stream; + type incoming-value-sync-body = list; +} \ No newline at end of file diff --git a/golem-rust/wit/deps/blobstore/world.wit b/golem-rust/wit/deps/blobstore/world.wit new file mode 100644 index 0000000..4391d68 --- /dev/null +++ b/golem-rust/wit/deps/blobstore/world.wit @@ -0,0 +1,5 @@ +package wasi:blobstore; + +world blob-store { + import blobstore; +} \ No newline at end of file diff --git a/golem-rust/wit/deps/cli/command.wit b/golem-rust/wit/deps/cli/command.wit new file mode 100644 index 0000000..d8005bd --- /dev/null +++ b/golem-rust/wit/deps/cli/command.wit @@ -0,0 +1,7 @@ +package wasi:cli@0.2.0; + +world command { + include imports; + + export run; +} diff --git a/golem-rust/wit/deps/cli/environment.wit b/golem-rust/wit/deps/cli/environment.wit new file mode 100644 index 0000000..7006523 --- /dev/null +++ b/golem-rust/wit/deps/cli/environment.wit @@ -0,0 +1,18 @@ +interface environment { + /// Get the POSIX-style environment variables. + /// + /// Each environment variable is provided as a pair of string variable names + /// and string value. + /// + /// Morally, these are a value import, but until value imports are available + /// in the component model, this import function should return the same + /// values each time it is called. + get-environment: func() -> list>; + + /// Get the POSIX-style arguments to the program. + get-arguments: func() -> list; + + /// Return a path that programs should use as their initial current working + /// directory, interpreting `.` as shorthand for this. + initial-cwd: func() -> option; +} diff --git a/golem-rust/wit/deps/cli/exit.wit b/golem-rust/wit/deps/cli/exit.wit new file mode 100644 index 0000000..d0c2b82 --- /dev/null +++ b/golem-rust/wit/deps/cli/exit.wit @@ -0,0 +1,4 @@ +interface exit { + /// Exit the current instance and any linked instances. + exit: func(status: result); +} diff --git a/golem-rust/wit/deps/cli/imports.wit b/golem-rust/wit/deps/cli/imports.wit new file mode 100644 index 0000000..083b84a --- /dev/null +++ b/golem-rust/wit/deps/cli/imports.wit @@ -0,0 +1,20 @@ +package wasi:cli@0.2.0; + +world imports { + include wasi:clocks/imports@0.2.0; + include wasi:filesystem/imports@0.2.0; + include wasi:sockets/imports@0.2.0; + include wasi:random/imports@0.2.0; + include wasi:io/imports@0.2.0; + + import environment; + import exit; + import stdin; + import stdout; + import stderr; + import terminal-input; + import terminal-output; + import terminal-stdin; + import terminal-stdout; + import terminal-stderr; +} diff --git a/golem-rust/wit/deps/cli/run.wit b/golem-rust/wit/deps/cli/run.wit new file mode 100644 index 0000000..a70ee8c --- /dev/null +++ b/golem-rust/wit/deps/cli/run.wit @@ -0,0 +1,4 @@ +interface run { + /// Run the program. + run: func() -> result; +} diff --git a/golem-rust/wit/deps/cli/stdio.wit b/golem-rust/wit/deps/cli/stdio.wit new file mode 100644 index 0000000..31ef35b --- /dev/null +++ b/golem-rust/wit/deps/cli/stdio.wit @@ -0,0 +1,17 @@ +interface stdin { + use wasi:io/streams@0.2.0.{input-stream}; + + get-stdin: func() -> input-stream; +} + +interface stdout { + use wasi:io/streams@0.2.0.{output-stream}; + + get-stdout: func() -> output-stream; +} + +interface stderr { + use wasi:io/streams@0.2.0.{output-stream}; + + get-stderr: func() -> output-stream; +} diff --git a/golem-rust/wit/deps/cli/terminal.wit b/golem-rust/wit/deps/cli/terminal.wit new file mode 100644 index 0000000..38c724e --- /dev/null +++ b/golem-rust/wit/deps/cli/terminal.wit @@ -0,0 +1,49 @@ +/// Terminal input. +/// +/// In the future, this may include functions for disabling echoing, +/// disabling input buffering so that keyboard events are sent through +/// immediately, querying supported features, and so on. +interface terminal-input { + /// The input side of a terminal. + resource terminal-input; +} + +/// Terminal output. +/// +/// In the future, this may include functions for querying the terminal +/// size, being notified of terminal size changes, querying supported +/// features, and so on. +interface terminal-output { + /// The output side of a terminal. + resource terminal-output; +} + +/// An interface providing an optional `terminal-input` for stdin as a +/// link-time authority. +interface terminal-stdin { + use terminal-input.{terminal-input}; + + /// If stdin is connected to a terminal, return a `terminal-input` handle + /// allowing further interaction with it. + get-terminal-stdin: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stdout as a +/// link-time authority. +interface terminal-stdout { + use terminal-output.{terminal-output}; + + /// If stdout is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + get-terminal-stdout: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stderr as a +/// link-time authority. +interface terminal-stderr { + use terminal-output.{terminal-output}; + + /// If stderr is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + get-terminal-stderr: func() -> option; +} diff --git a/golem-rust/wit/deps/clocks/monotonic-clock.wit b/golem-rust/wit/deps/clocks/monotonic-clock.wit new file mode 100644 index 0000000..4e4dc3a --- /dev/null +++ b/golem-rust/wit/deps/clocks/monotonic-clock.wit @@ -0,0 +1,45 @@ +package wasi:clocks@0.2.0; +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +/// +/// It is intended for measuring elapsed time. +interface monotonic-clock { + use wasi:io/poll@0.2.0.{pollable}; + + /// An instant in time, in nanoseconds. An instant is relative to an + /// unspecified initial value, and can only be compared to instances from + /// the same monotonic-clock. + type instant = u64; + + /// A duration of time, in nanoseconds. + type duration = u64; + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + now: func() -> instant; + + /// Query the resolution of the clock. Returns the duration of time + /// corresponding to a clock tick. + resolution: func() -> duration; + + /// Create a `pollable` which will resolve once the specified instant + /// occured. + subscribe-instant: func( + when: instant, + ) -> pollable; + + /// Create a `pollable` which will resolve once the given duration has + /// elapsed, starting at the time at which this function was called. + /// occured. + subscribe-duration: func( + when: duration, + ) -> pollable; +} diff --git a/golem-rust/wit/deps/clocks/wall-clock.wit b/golem-rust/wit/deps/clocks/wall-clock.wit new file mode 100644 index 0000000..440ca0f --- /dev/null +++ b/golem-rust/wit/deps/clocks/wall-clock.wit @@ -0,0 +1,42 @@ +package wasi:clocks@0.2.0; +/// WASI Wall Clock is a clock API intended to let users query the current +/// time. The name "wall" makes an analogy to a "clock on the wall", which +/// is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A wall clock is a clock which measures the date and time according to +/// some external reference. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +interface wall-clock { + /// A time and date in seconds plus nanoseconds. + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + now: func() -> datetime; + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + resolution: func() -> datetime; +} diff --git a/golem-rust/wit/deps/clocks/world.wit b/golem-rust/wit/deps/clocks/world.wit new file mode 100644 index 0000000..c022457 --- /dev/null +++ b/golem-rust/wit/deps/clocks/world.wit @@ -0,0 +1,6 @@ +package wasi:clocks@0.2.0; + +world imports { + import monotonic-clock; + import wall-clock; +} diff --git a/golem-rust/wit/deps/filesystem/preopens.wit b/golem-rust/wit/deps/filesystem/preopens.wit new file mode 100644 index 0000000..da801f6 --- /dev/null +++ b/golem-rust/wit/deps/filesystem/preopens.wit @@ -0,0 +1,8 @@ +package wasi:filesystem@0.2.0; + +interface preopens { + use types.{descriptor}; + + /// Return the set of preopened directories, and their path. + get-directories: func() -> list>; +} diff --git a/golem-rust/wit/deps/filesystem/types.wit b/golem-rust/wit/deps/filesystem/types.wit new file mode 100644 index 0000000..11108fc --- /dev/null +++ b/golem-rust/wit/deps/filesystem/types.wit @@ -0,0 +1,634 @@ +package wasi:filesystem@0.2.0; +/// WASI filesystem is a filesystem API primarily intended to let users run WASI +/// programs that access their files on their existing filesystems, without +/// significant overhead. +/// +/// It is intended to be roughly portable between Unix-family platforms and +/// Windows, though it does not hide many of the major differences. +/// +/// Paths are passed as interface-type `string`s, meaning they must consist of +/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +/// paths which are not accessible by this API. +/// +/// The directory separator in WASI is always the forward-slash (`/`). +/// +/// All paths in WASI are relative paths, and are interpreted relative to a +/// `descriptor` referring to a base directory. If a `path` argument to any WASI +/// function starts with `/`, or if any step of resolving a `path`, including +/// `..` and symbolic link steps, reaches a directory outside of the base +/// directory, or reaches a symlink to an absolute or rooted path in the +/// underlying filesystem, the function fails with `error-code::not-permitted`. +/// +/// For more information about WASI path resolution and sandboxing, see +/// [WASI filesystem path resolution]. +/// +/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +interface types { + use wasi:io/streams@0.2.0.{input-stream, output-stream, error}; + use wasi:clocks/wall-clock@0.2.0.{datetime}; + + /// File size or length of a region within a file. + type filesize = u64; + + /// The type of a filesystem object referenced by a descriptor. + /// + /// Note: This was called `filetype` in earlier versions of WASI. + enum descriptor-type { + /// The type of the descriptor or file is unknown or is different from + /// any of the other types specified. + unknown, + /// The descriptor refers to a block device inode. + block-device, + /// The descriptor refers to a character device inode. + character-device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic-link, + /// The descriptor refers to a regular file inode. + regular-file, + /// The descriptor refers to a socket. + socket, + } + + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + flags descriptor-flags { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + file-integrity-sync, + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + data-integrity-sync, + /// Requests that reads be performed at the same level of integrety + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + requested-write-sync, + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + mutate-directory, + } + + /// File attributes. + /// + /// Note: This was called `filestat` in earlier versions of WASI. + record descriptor-stat { + /// File type. + %type: descriptor-type, + /// Number of hard links to the file. + link-count: link-count, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + data-access-timestamp: option, + /// Last data modification timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + data-modification-timestamp: option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + status-change-timestamp: option, + } + + /// Flags determining the method of how paths are resolved. + flags path-flags { + /// As long as the resolved path corresponds to a symbolic link, it is + /// expanded. + symlink-follow, + } + + /// Open flags used by `open-at`. + flags open-flags { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + create, + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + directory, + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + exclusive, + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + truncate, + } + + /// Number of hard links to an inode. + type link-count = u64; + + /// When setting a timestamp, this gives the value to set it to. + variant new-timestamp { + /// Leave the timestamp set to its previous value. + no-change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(datetime), + } + + /// A directory entry. + record directory-entry { + /// The type of the file referred to by this directory entry. + %type: descriptor-type, + + /// The name of the object. + name: string, + } + + /// Error codes returned by functions, similar to `errno` in POSIX. + /// Not all of these error codes are returned by the functions provided by this + /// API; some are used in higher-level library layers, and others are provided + /// merely for alignment with POSIX. + enum error-code { + /// Permission denied, similar to `EACCES` in POSIX. + access, + /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX. + would-block, + /// Connection already in progress, similar to `EALREADY` in POSIX. + already, + /// Bad descriptor, similar to `EBADF` in POSIX. + bad-descriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + busy, + /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. + deadlock, + /// Storage quota exceeded, similar to `EDQUOT` in POSIX. + quota, + /// File exists, similar to `EEXIST` in POSIX. + exist, + /// File too large, similar to `EFBIG` in POSIX. + file-too-large, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + illegal-byte-sequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + in-progress, + /// Interrupted function, similar to `EINTR` in POSIX. + interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + invalid, + /// I/O error, similar to `EIO` in POSIX. + io, + /// Is a directory, similar to `EISDIR` in POSIX. + is-directory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + loop, + /// Too many links, similar to `EMLINK` in POSIX. + too-many-links, + /// Message too large, similar to `EMSGSIZE` in POSIX. + message-size, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + name-too-long, + /// No such device, similar to `ENODEV` in POSIX. + no-device, + /// No such file or directory, similar to `ENOENT` in POSIX. + no-entry, + /// No locks available, similar to `ENOLCK` in POSIX. + no-lock, + /// Not enough space, similar to `ENOMEM` in POSIX. + insufficient-memory, + /// No space left on device, similar to `ENOSPC` in POSIX. + insufficient-space, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + not-directory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + not-empty, + /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + not-recoverable, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + unsupported, + /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + no-tty, + /// No such device or address, similar to `ENXIO` in POSIX. + no-such-device, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + not-permitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + pipe, + /// Read-only file system, similar to `EROFS` in POSIX. + read-only, + /// Invalid seek, similar to `ESPIPE` in POSIX. + invalid-seek, + /// Text file busy, similar to `ETXTBSY` in POSIX. + text-file-busy, + /// Cross-device link, similar to `EXDEV` in POSIX. + cross-device, + } + + /// File or memory access pattern advisory information. + enum advice { + /// The application has no advice to give on its behavior with respect + /// to the specified data. + normal, + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random + /// order. + random, + /// The application expects to access the specified data in the near + /// future. + will-need, + /// The application expects that it will not access the specified data + /// in the near future. + dont-need, + /// The application expects to access the specified data once and then + /// not reuse it thereafter. + no-reuse, + } + + /// A 128-bit hash value, split into parts because wasm doesn't have a + /// 128-bit integer type. + record metadata-hash-value { + /// 64 bits of a 128-bit hash value. + lower: u64, + /// Another 64 bits of a 128-bit hash value. + upper: u64, + } + + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + resource descriptor { + /// Return a stream for reading from a file, if available. + /// + /// May fail with an error-code describing why the file cannot be read. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. + read-via-stream: func( + /// The offset within the file at which to start reading. + offset: filesize, + ) -> result; + + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. + /// + /// Note: This allows using `write-stream`, which is similar to `write` in + /// POSIX. + write-via-stream: func( + /// The offset within the file at which to start writing. + offset: filesize, + ) -> result; + + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. + /// + /// Note: This allows using `write-stream`, which is similar to `write` with + /// `O_APPEND` in in POSIX. + append-via-stream: func() -> result; + + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + advise: func( + /// The offset within the file to which the advisory applies. + offset: filesize, + /// The length of the region to which the advisory applies. + length: filesize, + /// The advice. + advice: advice + ) -> result<_, error-code>; + + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + sync-data: func() -> result<_, error-code>; + + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-flags: func() -> result; + + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-type: func() -> result; + + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + set-size: func(size: filesize) -> result<_, error-code>; + + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + set-times: func( + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code>; + + /// Read from a descriptor, without using and updating the descriptor's offset. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// file was reached. The returned list will contain up to `length` bytes; it + /// may return fewer than requested, if the end of the file is reached or + /// if the I/O operation is interrupted. + /// + /// In the future, this may change to return a `stream`. + /// + /// Note: This is similar to `pread` in POSIX. + read: func( + /// The maximum number of bytes to read. + length: filesize, + /// The offset within the file at which to read. + offset: filesize, + ) -> result, bool>, error-code>; + + /// Write to a descriptor, without using and updating the descriptor's offset. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// In the future, this may change to take a `stream`. + /// + /// Note: This is similar to `pwrite` in POSIX. + write: func( + /// Data to write + buffer: list, + /// The offset within the file at which to write. + offset: filesize, + ) -> result; + + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + read-directory: func() -> result; + + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + sync: func() -> result<_, error-code>; + + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + create-directory-at: func( + /// The relative path at which to create the directory. + path: string, + ) -> result<_, error-code>; + + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX, except that it does not return + /// device and inode information. For testing whether two descriptors refer to + /// the same underlying filesystem object, use `is-same-object`. To obtain + /// additional data that can be used do determine whether a file has been + /// modified, use `metadata-hash`. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + stat: func() -> result; + + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX, except that it does not + /// return device and inode information. See the `stat` description for a + /// discussion of alternatives. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + stat-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result; + + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + set-times-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to operate on. + path: string, + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code>; + + /// Create a hard link. + /// + /// Note: This is similar to `linkat` in POSIX. + link-at: func( + /// Flags determining the method of how the path is resolved. + old-path-flags: path-flags, + /// The relative source path from which to link. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path at which to create the hard link. + new-path: string, + ) -> result<_, error-code>; + + /// Open a file or directory. + /// + /// The returned descriptor is not guaranteed to be the lowest-numbered + /// descriptor not currently open/ it is randomized to prevent applications + /// from depending on making assumptions about indexes, since this is + /// error-prone in multi-threaded contexts. The returned descriptor is + /// guaranteed to be less than 2**31. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + open-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the object to open. + path: string, + /// The method by which to open the file. + open-flags: open-flags, + /// Flags to use for the resulting descriptor. + %flags: descriptor-flags, + ) -> result; + + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + readlink-at: func( + /// The relative path of the symbolic link from which to read. + path: string, + ) -> result; + + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + remove-directory-at: func( + /// The relative path to a directory to remove. + path: string, + ) -> result<_, error-code>; + + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + rename-at: func( + /// The relative source path of the file or directory to rename. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path to which to rename the file or directory. + new-path: string, + ) -> result<_, error-code>; + + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + symlink-at: func( + /// The contents of the symbolic link. + old-path: string, + /// The relative destination path at which to create the symbolic link. + new-path: string, + ) -> result<_, error-code>; + + /// Unlink a filesystem object that is not a directory. + /// + /// Return `error-code::is-directory` if the path refers to a directory. + /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + unlink-file-at: func( + /// The relative path to a file to unlink. + path: string, + ) -> result<_, error-code>; + + /// Test whether two descriptors refer to the same filesystem object. + /// + /// In POSIX, this corresponds to testing whether the two descriptors have the + /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + /// wasi-filesystem does not expose device and inode numbers, so this function + /// may be used instead. + is-same-object: func(other: borrow) -> bool; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a descriptor. + /// + /// This returns a hash of the last-modification timestamp and file size, and + /// may also include the inode number, device number, birth timestamp, and + /// other metadata fields that may change when the file is modified or + /// replaced. It may also include a secret value chosen by the + /// implementation and not otherwise exposed. + /// + /// Implementations are encourated to provide the following properties: + /// + /// - If the file is not modified or replaced, the computed hash value should + /// usually not change. + /// - If the object is modified or replaced, the computed hash value should + /// usually change. + /// - The inputs to the hash should not be easily computable from the + /// computed hash. + /// + /// However, none of these is required. + metadata-hash: func() -> result; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a directory descriptor and a relative path. + /// + /// This performs the same hash computation as `metadata-hash`. + metadata-hash-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result; + } + + /// A stream of directory entries. + resource directory-entry-stream { + /// Read a single directory entry from a `directory-entry-stream`. + read-directory-entry: func() -> result, error-code>; + } + + /// Attempts to extract a filesystem-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// filesystem-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are filesystem-related errors. + filesystem-error-code: func(err: borrow) -> option; +} diff --git a/golem-rust/wit/deps/filesystem/world.wit b/golem-rust/wit/deps/filesystem/world.wit new file mode 100644 index 0000000..663f579 --- /dev/null +++ b/golem-rust/wit/deps/filesystem/world.wit @@ -0,0 +1,6 @@ +package wasi:filesystem@0.2.0; + +world imports { + import types; + import preopens; +} diff --git a/golem-rust/wit/deps/golem/golem-host.wit b/golem-rust/wit/deps/golem/golem-host.wit new file mode 100644 index 0000000..94eb6c1 --- /dev/null +++ b/golem-rust/wit/deps/golem/golem-host.wit @@ -0,0 +1,215 @@ +package golem:api@0.2.0; + +/// The Golem host API provides low level access to Golem specific features such as promises and control over +/// the durability and transactional guarantees the executor provides. +interface host { + use golem:rpc/types@0.1.0.{uri}; + use wasi:clocks/monotonic-clock@0.2.0.{duration}; + + /// An index into the persistent log storing all performed operations of a worker + type oplog-index = u64; + + /// A promise ID is a value that can be passed to an external Golem API to complete that promise + /// from an arbitrary external source, while Golem workers can await for this completion. + record promise-id { + worker-id: worker-id, + oplog-idx: oplog-index, + } + + /// Represents a Golem worker + record worker-id { + template-id: template-id, + worker-name: string + } + + + /// Represents a Golem template + record template-id { + uuid: uuid, + } + + /// UUID + record uuid { + high-bits: u64, + low-bits: u64 + } + + /// Configures how the executor retries failures + record retry-policy { + /// The maximum number of retries before the worker becomes permanently failed + max-attempts: u32, + /// The minimum delay between retries (applied to the first retry) + min-delay: duration, + /// The maximum delay between retries + max-delay: duration, + /// Multiplier applied to the delay on each retry to implement exponential backoff + multiplier: u32 + } + + /// Configurable persistence level for workers + variant persistence-level { + persist-nothing, + persist-remote-side-effects, + smart + } + + enum filter-comparator { + equal, + not-equal, + greater-equal, + greater, + less-equal, + less + } + + enum string-filter-comparator { + equal, + not-equal, + like, + not-like + } + + enum worker-status { + /// The worker is running an invoked function + running, + /// The worker is ready to run an invoked function + idle, + /// An invocation is active but waiting for something (sleeping, waiting for a promise) + suspended, + /// The last invocation was interrupted but will be resumed + interrupted, + /// The last invocation failed and a retry was scheduled + retrying, + /// The last invocation failed and the worker can no longer be used + failed, + /// The worker exited after a successful invocation and can no longer be invoked + exited, + } + + record worker-name-filter { + comparator: string-filter-comparator, + value: string + } + + record worker-status-filter { + comparator: filter-comparator, + value: worker-status + } + + record worker-version-filter { + comparator: filter-comparator, + value: u64 + } + + record worker-created-at-filter { + comparator: filter-comparator, + value: u64 + } + + record worker-env-filter { + name: string, + comparator: string-filter-comparator, + value: string + } + + variant worker-property-filter { + name(worker-name-filter), + status(worker-status-filter), + version(worker-version-filter), + created-at(worker-created-at-filter), + env(worker-env-filter) + } + + record worker-all-filter { + filters: list + } + + record worker-any-filter { + filters: list + } + + record worker-metadata { + worker-id: worker-id, + args: list, + env: list>, + status: worker-status, + template-version: u64, + retry-count: u64 + } + + resource get-workers { + constructor(template-id: template-id, filter: option, precise: bool); + + get-next: func() -> option>; + } + + /// Create a new promise + golem-create-promise: func() -> promise-id; + + /// Suspends execution until the given promise gets completed, and returns the payload passed to + /// the promise completion. + golem-await-promise: func(promise-id: promise-id) -> list; + + /// Completes the given promise with the given payload. Returns true if the promise was completed, false + /// if the promise was already completed. The payload is passed to the worker that is awaiting the promise. + golem-complete-promise: func(promise-id: promise-id, data: list) -> bool; + + /// Deletes the given promise + golem-delete-promise: func(promise-id: promise-id) -> (); + + /// Returns a Golem worker URI that can be used to invoke a given function on the current worker + get-self-uri: func(function-name: string) -> uri; + + /// Returns the current position in the persistent op log + get-oplog-index: func() -> oplog-index; + + /// Makes the current worker travel back in time and continue execution from the given position in the persistent + /// op log. + set-oplog-index: func(oplog-idx: oplog-index) -> (); + + /// Blocks the execution until the oplog has been written to at least the specified number of replicas, + /// or the maximum number of replicas if the requested number is higher. + oplog-commit: func(replicas: u8) -> (); + + /// Marks the beginning of an atomic operation. + /// In case of a failure within the region selected by `mark-begin-operation` and `mark-end-operation` + /// the whole region will be reexecuted on retry. + /// The end of the region is when `mark-end-operation` is called with the returned oplog-index. + mark-begin-operation: func() -> oplog-index; + + /// Commits this atomic operation. After `mark-end-operation` is called for a given index, further calls + /// with the same parameter will do nothing. + mark-end-operation: func(begin: oplog-index) -> (); + + /// Gets the current retry policy associated with the worker + get-retry-policy: func() -> retry-policy; + + /// Overrides the current retry policy associated with the worker. Following this call, `get-retry-policy` will return the + /// new retry policy. + set-retry-policy: func(new-retry-policy: retry-policy) -> (); + + /// Gets the worker's current persistence level. + get-oplog-persistence-level: func() -> persistence-level; + + /// Sets the worker's current persistence level. This can increase the performance of execution in cases where durable + /// execution is not required. + set-oplog-persistence-level: func(new-persistence-level: persistence-level) -> (); + + /// Gets the current idempotence mode. See `set-idempotence-mode` for details. + get-idempotence-mode: func() -> bool; + + /// Sets the current idempotence mode. The default is true. + /// True means side-effects are treated idempotent and Golem guarantees at-least-once semantics. + /// In case of false the executor provides at-most-once semantics, failing the worker in case it is + /// not known if the side effect was already executed. + set-idempotence-mode: func(idempotent: bool) -> (); + + /// Generates an idempotency key. This operation will never be replayed — + /// i.e. not only is this key generated, but it is persisted and committed, such that the key can be used in third-party systems (e.g. payment processing) + /// to introduce idempotence. + generate-idempotency-key: func() -> uuid; +} + +world golem-host { + import host; +} \ No newline at end of file diff --git a/golem-rust/wit/deps/http/handler.wit b/golem-rust/wit/deps/http/handler.wit new file mode 100644 index 0000000..a34a064 --- /dev/null +++ b/golem-rust/wit/deps/http/handler.wit @@ -0,0 +1,43 @@ +/// This interface defines a handler of incoming HTTP Requests. It should +/// be exported by components which can respond to HTTP Requests. +interface incoming-handler { + use types.{incoming-request, response-outparam}; + + /// This function is invoked with an incoming HTTP Request, and a resource + /// `response-outparam` which provides the capability to reply with an HTTP + /// Response. The response is sent by calling the `response-outparam.set` + /// method, which allows execution to continue after the response has been + /// sent. This enables both streaming to the response body, and performing other + /// work. + /// + /// The implementor of this function must write a response to the + /// `response-outparam` before returning, or else the caller will respond + /// with an error on its behalf. + handle: func( + request: incoming-request, + response-out: response-outparam + ); +} + +/// This interface defines a handler of outgoing HTTP Requests. It should be +/// imported by components which wish to make HTTP Requests. +interface outgoing-handler { + use types.{ + outgoing-request, request-options, future-incoming-response, error-code + }; + + /// This function is invoked with an outgoing HTTP Request, and it returns + /// a resource `future-incoming-response` which represents an HTTP Response + /// which may arrive in the future. + /// + /// The `options` argument accepts optional parameters for the HTTP + /// protocol's transport layer. + /// + /// This function may return an error if the `outgoing-request` is invalid + /// or not allowed to be made. Otherwise, protocol errors are reported + /// through the `future-incoming-response`. + handle: func( + request: outgoing-request, + options: option + ) -> result; +} diff --git a/golem-rust/wit/deps/http/proxy.wit b/golem-rust/wit/deps/http/proxy.wit new file mode 100644 index 0000000..687c24d --- /dev/null +++ b/golem-rust/wit/deps/http/proxy.wit @@ -0,0 +1,32 @@ +package wasi:http@0.2.0; + +/// The `wasi:http/proxy` world captures a widely-implementable intersection of +/// hosts that includes HTTP forward and reverse proxies. Components targeting +/// this world may concurrently stream in and out any number of incoming and +/// outgoing HTTP requests. +world proxy { + /// HTTP proxies have access to time and randomness. + include wasi:clocks/imports@0.2.0; + import wasi:random/random@0.2.0; + + /// Proxies have standard output and error streams which are expected to + /// terminate in a developer-facing console provided by the host. + import wasi:cli/stdout@0.2.0; + import wasi:cli/stderr@0.2.0; + + /// TODO: this is a temporary workaround until component tooling is able to + /// gracefully handle the absence of stdin. Hosts must return an eof stream + /// for this import, which is what wasi-libc + tooling will do automatically + /// when this import is properly removed. + import wasi:cli/stdin@0.2.0; + + /// This is the default handler to use when user code simply wants to make an + /// HTTP request (e.g., via `fetch()`). + import outgoing-handler; + + /// The host delivers incoming HTTP requests to a component by calling the + /// `handle` function of this exported interface. A host may arbitrarily reuse + /// or not reuse component instance when delivering incoming HTTP requests and + /// thus a component must be able to handle 0..N calls to `handle`. + export incoming-handler; +} diff --git a/golem-rust/wit/deps/http/types.wit b/golem-rust/wit/deps/http/types.wit new file mode 100644 index 0000000..755ac6a --- /dev/null +++ b/golem-rust/wit/deps/http/types.wit @@ -0,0 +1,570 @@ +/// This interface defines all of the types and methods for implementing +/// HTTP Requests and Responses, both incoming and outgoing, as well as +/// their headers, trailers, and bodies. +interface types { + use wasi:clocks/monotonic-clock@0.2.0.{duration}; + use wasi:io/streams@0.2.0.{input-stream, output-stream}; + use wasi:io/error@0.2.0.{error as io-error}; + use wasi:io/poll@0.2.0.{pollable}; + + /// This type corresponds to HTTP standard Methods. + variant method { + get, + head, + post, + put, + delete, + connect, + options, + trace, + patch, + other(string) + } + + /// This type corresponds to HTTP standard Related Schemes. + variant scheme { + HTTP, + HTTPS, + other(string) + } + + /// These cases are inspired by the IANA HTTP Proxy Error Types: + /// https://www.iana.org/assignments/http-proxy-status/http-proxy-status.xhtml#table-http-proxy-error-types + variant error-code { + DNS-timeout, + DNS-error(DNS-error-payload), + destination-not-found, + destination-unavailable, + destination-IP-prohibited, + destination-IP-unroutable, + connection-refused, + connection-terminated, + connection-timeout, + connection-read-timeout, + connection-write-timeout, + connection-limit-reached, + TLS-protocol-error, + TLS-certificate-error, + TLS-alert-received(TLS-alert-received-payload), + HTTP-request-denied, + HTTP-request-length-required, + HTTP-request-body-size(option), + HTTP-request-method-invalid, + HTTP-request-URI-invalid, + HTTP-request-URI-too-long, + HTTP-request-header-section-size(option), + HTTP-request-header-size(option), + HTTP-request-trailer-section-size(option), + HTTP-request-trailer-size(field-size-payload), + HTTP-response-incomplete, + HTTP-response-header-section-size(option), + HTTP-response-header-size(field-size-payload), + HTTP-response-body-size(option), + HTTP-response-trailer-section-size(option), + HTTP-response-trailer-size(field-size-payload), + HTTP-response-transfer-coding(option), + HTTP-response-content-coding(option), + HTTP-response-timeout, + HTTP-upgrade-failed, + HTTP-protocol-error, + loop-detected, + configuration-error, + /// This is a catch-all error for anything that doesn't fit cleanly into a + /// more specific case. It also includes an optional string for an + /// unstructured description of the error. Users should not depend on the + /// string for diagnosing errors, as it's not required to be consistent + /// between implementations. + internal-error(option) + } + + /// Defines the case payload type for `DNS-error` above: + record DNS-error-payload { + rcode: option, + info-code: option + } + + /// Defines the case payload type for `TLS-alert-received` above: + record TLS-alert-received-payload { + alert-id: option, + alert-message: option + } + + /// Defines the case payload type for `HTTP-response-{header,trailer}-size` above: + record field-size-payload { + field-name: option, + field-size: option + } + + /// Attempts to extract a http-related `error` from the wasi:io `error` + /// provided. + /// + /// Stream operations which return + /// `wasi:io/stream/stream-error::last-operation-failed` have a payload of + /// type `wasi:io/error/error` with more information about the operation + /// that failed. This payload can be passed through to this function to see + /// if there's http-related information about the error to return. + /// + /// Note that this function is fallible because not all io-errors are + /// http-related errors. + http-error-code: func(err: borrow) -> option; + + /// This type enumerates the different kinds of errors that may occur when + /// setting or appending to a `fields` resource. + variant header-error { + /// This error indicates that a `field-key` or `field-value` was + /// syntactically invalid when used with an operation that sets headers in a + /// `fields`. + invalid-syntax, + + /// This error indicates that a forbidden `field-key` was used when trying + /// to set a header in a `fields`. + forbidden, + + /// This error indicates that the operation on the `fields` was not + /// permitted because the fields are immutable. + immutable, + } + + /// Field keys are always strings. + type field-key = string; + + /// Field values should always be ASCII strings. However, in + /// reality, HTTP implementations often have to interpret malformed values, + /// so they are provided as a list of bytes. + type field-value = list; + + /// This following block defines the `fields` resource which corresponds to + /// HTTP standard Fields. Fields are a common representation used for both + /// Headers and Trailers. + /// + /// A `fields` may be mutable or immutable. A `fields` created using the + /// constructor, `from-list`, or `clone` will be mutable, but a `fields` + /// resource given by other means (including, but not limited to, + /// `incoming-request.headers`, `outgoing-request.headers`) might be be + /// immutable. In an immutable fields, the `set`, `append`, and `delete` + /// operations will fail with `header-error.immutable`. + resource fields { + + /// Construct an empty HTTP Fields. + /// + /// The resulting `fields` is mutable. + constructor(); + + /// Construct an HTTP Fields. + /// + /// The resulting `fields` is mutable. + /// + /// The list represents each key-value pair in the Fields. Keys + /// which have multiple values are represented by multiple entries in this + /// list with the same key. + /// + /// The tuple is a pair of the field key, represented as a string, and + /// Value, represented as a list of bytes. In a valid Fields, all keys + /// and values are valid UTF-8 strings. However, values are not always + /// well-formed, so they are represented as a raw list of bytes. + /// + /// An error result will be returned if any header or value was + /// syntactically invalid, or if a header was forbidden. + from-list: static func( + entries: list> + ) -> result; + + /// Get all of the values corresponding to a key. If the key is not present + /// in this `fields`, an empty list is returned. However, if the key is + /// present but empty, this is represented by a list with one or more + /// empty field-values present. + get: func(name: field-key) -> list; + + /// Returns `true` when the key is present in this `fields`. If the key is + /// syntactically invalid, `false` is returned. + has: func(name: field-key) -> bool; + + /// Set all of the values for a key. Clears any existing values for that + /// key, if they have been set. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. + set: func(name: field-key, value: list) -> result<_, header-error>; + + /// Delete all values for a key. Does nothing if no values for the key + /// exist. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. + delete: func(name: field-key) -> result<_, header-error>; + + /// Append a value for a key. Does not change or delete any existing + /// values for that key. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. + append: func(name: field-key, value: field-value) -> result<_, header-error>; + + /// Retrieve the full set of keys and values in the Fields. Like the + /// constructor, the list represents each key-value pair. + /// + /// The outer list represents each key-value pair in the Fields. Keys + /// which have multiple values are represented by multiple entries in this + /// list with the same key. + entries: func() -> list>; + + /// Make a deep copy of the Fields. Equivelant in behavior to calling the + /// `fields` constructor on the return value of `entries`. The resulting + /// `fields` is mutable. + clone: func() -> fields; + } + + /// Headers is an alias for Fields. + type headers = fields; + + /// Trailers is an alias for Fields. + type trailers = fields; + + /// Represents an incoming HTTP Request. + resource incoming-request { + + /// Returns the method of the incoming request. + method: func() -> method; + + /// Returns the path with query parameters from the request, as a string. + path-with-query: func() -> option; + + /// Returns the protocol scheme from the request. + scheme: func() -> option; + + /// Returns the authority from the request, if it was present. + authority: func() -> option; + + /// Get the `headers` associated with the request. + /// + /// The returned `headers` resource is immutable: `set`, `append`, and + /// `delete` operations will fail with `header-error.immutable`. + /// + /// The `headers` returned are a child resource: it must be dropped before + /// the parent `incoming-request` is dropped. Dropping this + /// `incoming-request` before all children are dropped will trap. + headers: func() -> headers; + + /// Gives the `incoming-body` associated with this request. Will only + /// return success at most once, and subsequent calls will return error. + consume: func() -> result; + } + + /// Represents an outgoing HTTP Request. + resource outgoing-request { + + /// Construct a new `outgoing-request` with a default `method` of `GET`, and + /// `none` values for `path-with-query`, `scheme`, and `authority`. + /// + /// * `headers` is the HTTP Headers for the Request. + /// + /// It is possible to construct, or manipulate with the accessor functions + /// below, an `outgoing-request` with an invalid combination of `scheme` + /// and `authority`, or `headers` which are not permitted to be sent. + /// It is the obligation of the `outgoing-handler.handle` implementation + /// to reject invalid constructions of `outgoing-request`. + constructor( + headers: headers + ); + + /// Returns the resource corresponding to the outgoing Body for this + /// Request. + /// + /// Returns success on the first call: the `outgoing-body` resource for + /// this `outgoing-request` can be retrieved at most once. Subsequent + /// calls will return error. + body: func() -> result; + + /// Get the Method for the Request. + method: func() -> method; + /// Set the Method for the Request. Fails if the string present in a + /// `method.other` argument is not a syntactically valid method. + set-method: func(method: method) -> result; + + /// Get the combination of the HTTP Path and Query for the Request. + /// When `none`, this represents an empty Path and empty Query. + path-with-query: func() -> option; + /// Set the combination of the HTTP Path and Query for the Request. + /// When `none`, this represents an empty Path and empty Query. Fails is the + /// string given is not a syntactically valid path and query uri component. + set-path-with-query: func(path-with-query: option) -> result; + + /// Get the HTTP Related Scheme for the Request. When `none`, the + /// implementation may choose an appropriate default scheme. + scheme: func() -> option; + /// Set the HTTP Related Scheme for the Request. When `none`, the + /// implementation may choose an appropriate default scheme. Fails if the + /// string given is not a syntactically valid uri scheme. + set-scheme: func(scheme: option) -> result; + + /// Get the HTTP Authority for the Request. A value of `none` may be used + /// with Related Schemes which do not require an Authority. The HTTP and + /// HTTPS schemes always require an authority. + authority: func() -> option; + /// Set the HTTP Authority for the Request. A value of `none` may be used + /// with Related Schemes which do not require an Authority. The HTTP and + /// HTTPS schemes always require an authority. Fails if the string given is + /// not a syntactically valid uri authority. + set-authority: func(authority: option) -> result; + + /// Get the headers associated with the Request. + /// + /// The returned `headers` resource is immutable: `set`, `append`, and + /// `delete` operations will fail with `header-error.immutable`. + /// + /// This headers resource is a child: it must be dropped before the parent + /// `outgoing-request` is dropped, or its ownership is transfered to + /// another component by e.g. `outgoing-handler.handle`. + headers: func() -> headers; + } + + /// Parameters for making an HTTP Request. Each of these parameters is + /// currently an optional timeout applicable to the transport layer of the + /// HTTP protocol. + /// + /// These timeouts are separate from any the user may use to bound a + /// blocking call to `wasi:io/poll.poll`. + resource request-options { + /// Construct a default `request-options` value. + constructor(); + + /// The timeout for the initial connect to the HTTP Server. + connect-timeout: func() -> option; + + /// Set the timeout for the initial connect to the HTTP Server. An error + /// return value indicates that this timeout is not supported. + set-connect-timeout: func(duration: option) -> result; + + /// The timeout for receiving the first byte of the Response body. + first-byte-timeout: func() -> option; + + /// Set the timeout for receiving the first byte of the Response body. An + /// error return value indicates that this timeout is not supported. + set-first-byte-timeout: func(duration: option) -> result; + + /// The timeout for receiving subsequent chunks of bytes in the Response + /// body stream. + between-bytes-timeout: func() -> option; + + /// Set the timeout for receiving subsequent chunks of bytes in the Response + /// body stream. An error return value indicates that this timeout is not + /// supported. + set-between-bytes-timeout: func(duration: option) -> result; + } + + /// Represents the ability to send an HTTP Response. + /// + /// This resource is used by the `wasi:http/incoming-handler` interface to + /// allow a Response to be sent corresponding to the Request provided as the + /// other argument to `incoming-handler.handle`. + resource response-outparam { + + /// Set the value of the `response-outparam` to either send a response, + /// or indicate an error. + /// + /// This method consumes the `response-outparam` to ensure that it is + /// called at most once. If it is never called, the implementation + /// will respond with an error. + /// + /// The user may provide an `error` to `response` to allow the + /// implementation determine how to respond with an HTTP error response. + set: static func( + param: response-outparam, + response: result, + ); + } + + /// This type corresponds to the HTTP standard Status Code. + type status-code = u16; + + /// Represents an incoming HTTP Response. + resource incoming-response { + + /// Returns the status code from the incoming response. + status: func() -> status-code; + + /// Returns the headers from the incoming response. + /// + /// The returned `headers` resource is immutable: `set`, `append`, and + /// `delete` operations will fail with `header-error.immutable`. + /// + /// This headers resource is a child: it must be dropped before the parent + /// `incoming-response` is dropped. + headers: func() -> headers; + + /// Returns the incoming body. May be called at most once. Returns error + /// if called additional times. + consume: func() -> result; + } + + /// Represents an incoming HTTP Request or Response's Body. + /// + /// A body has both its contents - a stream of bytes - and a (possibly + /// empty) set of trailers, indicating that the full contents of the + /// body have been received. This resource represents the contents as + /// an `input-stream` and the delivery of trailers as a `future-trailers`, + /// and ensures that the user of this interface may only be consuming either + /// the body contents or waiting on trailers at any given time. + resource incoming-body { + + /// Returns the contents of the body, as a stream of bytes. + /// + /// Returns success on first call: the stream representing the contents + /// can be retrieved at most once. Subsequent calls will return error. + /// + /// The returned `input-stream` resource is a child: it must be dropped + /// before the parent `incoming-body` is dropped, or consumed by + /// `incoming-body.finish`. + /// + /// This invariant ensures that the implementation can determine whether + /// the user is consuming the contents of the body, waiting on the + /// `future-trailers` to be ready, or neither. This allows for network + /// backpressure is to be applied when the user is consuming the body, + /// and for that backpressure to not inhibit delivery of the trailers if + /// the user does not read the entire body. + %stream: func() -> result; + + /// Takes ownership of `incoming-body`, and returns a `future-trailers`. + /// This function will trap if the `input-stream` child is still alive. + finish: static func(this: incoming-body) -> future-trailers; + } + + /// Represents a future which may eventaully return trailers, or an error. + /// + /// In the case that the incoming HTTP Request or Response did not have any + /// trailers, this future will resolve to the empty set of trailers once the + /// complete Request or Response body has been received. + resource future-trailers { + + /// Returns a pollable which becomes ready when either the trailers have + /// been received, or an error has occured. When this pollable is ready, + /// the `get` method will return `some`. + subscribe: func() -> pollable; + + /// Returns the contents of the trailers, or an error which occured, + /// once the future is ready. + /// + /// The outer `option` represents future readiness. Users can wait on this + /// `option` to become `some` using the `subscribe` method. + /// + /// The outer `result` is used to retrieve the trailers or error at most + /// once. It will be success on the first call in which the outer option + /// is `some`, and error on subsequent calls. + /// + /// The inner `result` represents that either the HTTP Request or Response + /// body, as well as any trailers, were received successfully, or that an + /// error occured receiving them. The optional `trailers` indicates whether + /// or not trailers were present in the body. + /// + /// When some `trailers` are returned by this method, the `trailers` + /// resource is immutable, and a child. Use of the `set`, `append`, or + /// `delete` methods will return an error, and the resource must be + /// dropped before the parent `future-trailers` is dropped. + get: func() -> option, error-code>>>; + } + + /// Represents an outgoing HTTP Response. + resource outgoing-response { + + /// Construct an `outgoing-response`, with a default `status-code` of `200`. + /// If a different `status-code` is needed, it must be set via the + /// `set-status-code` method. + /// + /// * `headers` is the HTTP Headers for the Response. + constructor(headers: headers); + + /// Get the HTTP Status Code for the Response. + status-code: func() -> status-code; + + /// Set the HTTP Status Code for the Response. Fails if the status-code + /// given is not a valid http status code. + set-status-code: func(status-code: status-code) -> result; + + /// Get the headers associated with the Request. + /// + /// The returned `headers` resource is immutable: `set`, `append`, and + /// `delete` operations will fail with `header-error.immutable`. + /// + /// This headers resource is a child: it must be dropped before the parent + /// `outgoing-request` is dropped, or its ownership is transfered to + /// another component by e.g. `outgoing-handler.handle`. + headers: func() -> headers; + + /// Returns the resource corresponding to the outgoing Body for this Response. + /// + /// Returns success on the first call: the `outgoing-body` resource for + /// this `outgoing-response` can be retrieved at most once. Subsequent + /// calls will return error. + body: func() -> result; + } + + /// Represents an outgoing HTTP Request or Response's Body. + /// + /// A body has both its contents - a stream of bytes - and a (possibly + /// empty) set of trailers, inducating the full contents of the body + /// have been sent. This resource represents the contents as an + /// `output-stream` child resource, and the completion of the body (with + /// optional trailers) with a static function that consumes the + /// `outgoing-body` resource, and ensures that the user of this interface + /// may not write to the body contents after the body has been finished. + /// + /// If the user code drops this resource, as opposed to calling the static + /// method `finish`, the implementation should treat the body as incomplete, + /// and that an error has occured. The implementation should propogate this + /// error to the HTTP protocol by whatever means it has available, + /// including: corrupting the body on the wire, aborting the associated + /// Request, or sending a late status code for the Response. + resource outgoing-body { + + /// Returns a stream for writing the body contents. + /// + /// The returned `output-stream` is a child resource: it must be dropped + /// before the parent `outgoing-body` resource is dropped (or finished), + /// otherwise the `outgoing-body` drop or `finish` will trap. + /// + /// Returns success on the first call: the `output-stream` resource for + /// this `outgoing-body` may be retrieved at most once. Subsequent calls + /// will return error. + write: func() -> result; + + /// Finalize an outgoing body, optionally providing trailers. This must be + /// called to signal that the response is complete. If the `outgoing-body` + /// is dropped without calling `outgoing-body.finalize`, the implementation + /// should treat the body as corrupted. + /// + /// Fails if the body's `outgoing-request` or `outgoing-response` was + /// constructed with a Content-Length header, and the contents written + /// to the body (via `write`) does not match the value given in the + /// Content-Length. + finish: static func( + this: outgoing-body, + trailers: option + ) -> result<_, error-code>; + } + + /// Represents a future which may eventaully return an incoming HTTP + /// Response, or an error. + /// + /// This resource is returned by the `wasi:http/outgoing-handler` interface to + /// provide the HTTP Response corresponding to the sent Request. + resource future-incoming-response { + /// Returns a pollable which becomes ready when either the Response has + /// been received, or an error has occured. When this pollable is ready, + /// the `get` method will return `some`. + subscribe: func() -> pollable; + + /// Returns the incoming HTTP Response, or an error, once one is ready. + /// + /// The outer `option` represents future readiness. Users can wait on this + /// `option` to become `some` using the `subscribe` method. + /// + /// The outer `result` is used to retrieve the response or error at most + /// once. It will be success on the first call in which the outer option + /// is `some`, and error on subsequent calls. + /// + /// The inner `result` represents that either the incoming HTTP Response + /// status and headers have recieved successfully, or that an error + /// occured. Errors may also occur while consuming the response body, + /// but those will be reported by the `incoming-body` and its + /// `output-stream` child. + get: func() -> option>>; + + } +} diff --git a/golem-rust/wit/deps/io/error.wit b/golem-rust/wit/deps/io/error.wit new file mode 100644 index 0000000..22e5b64 --- /dev/null +++ b/golem-rust/wit/deps/io/error.wit @@ -0,0 +1,34 @@ +package wasi:io@0.2.0; + + +interface error { + /// A resource which represents some error information. + /// + /// The only method provided by this resource is `to-debug-string`, + /// which provides some human-readable information about the error. + /// + /// In the `wasi:io` package, this resource is returned through the + /// `wasi:io/streams/stream-error` type. + /// + /// To provide more specific error information, other interfaces may + /// provide functions to further "downcast" this error into more specific + /// error information. For example, `error`s returned in streams derived + /// from filesystem types to be described using the filesystem's own + /// error-code type, using the function + /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter + /// `borrow` and returns + /// `option`. + /// + /// The set of functions which can "downcast" an `error` into a more + /// concrete type is open. + resource error { + /// Returns a string that is suitable to assist humans in debugging + /// this error. + /// + /// WARNING: The returned string should not be consumed mechanically! + /// It may change across platforms, hosts, or other implementation + /// details. Parsing this string is a major platform-compatibility + /// hazard. + to-debug-string: func() -> string; + } +} diff --git a/golem-rust/wit/deps/io/poll.wit b/golem-rust/wit/deps/io/poll.wit new file mode 100644 index 0000000..c982f1a --- /dev/null +++ b/golem-rust/wit/deps/io/poll.wit @@ -0,0 +1,41 @@ +package wasi:io@0.2.0; + +/// A poll API intended to let users wait for I/O events on multiple handles +/// at once. +interface poll { + /// `pollable` epresents a single I/O event which may be ready, or not. + resource pollable { + + /// Return the readiness of a pollable. This function never blocks. + /// + /// Returns `true` when the pollable is ready, and `false` otherwise. + ready: func() -> bool; + + /// `block` returns immediately if the pollable is ready, and otherwise + /// blocks until ready. + /// + /// This function is equivalent to calling `poll.poll` on a list + /// containing only this pollable. + block: func(); + } + + /// Poll for completion on a set of pollables. + /// + /// This function takes a list of pollables, which identify I/O sources of + /// interest, and waits until one or more of the events is ready for I/O. + /// + /// The result `list` contains one or more indices of handles in the + /// argument list that is ready for I/O. + /// + /// If the list contains more elements than can be indexed with a `u32` + /// value, this function traps. + /// + /// A timeout can be implemented by adding a pollable from the + /// wasi-clocks API to the list. + /// + /// This function does not return a `result`; polling in itself does not + /// do any I/O so it doesn't fail. If any of the I/O sources identified by + /// the pollables has an error, it is indicated by marking the source as + /// being reaedy for I/O. + poll: func(in: list>) -> list; +} diff --git a/golem-rust/wit/deps/io/streams.wit b/golem-rust/wit/deps/io/streams.wit new file mode 100644 index 0000000..c797e37 --- /dev/null +++ b/golem-rust/wit/deps/io/streams.wit @@ -0,0 +1,251 @@ +package wasi:io@0.2.0; + +/// WASI I/O is an I/O abstraction API which is currently focused on providing +/// stream types. +/// +/// In the future, the component model is expected to add built-in stream types; +/// when it does, they are expected to subsume this API. +interface streams { + use error.{error}; + use poll.{pollable}; + + /// An error for input-stream and output-stream operations. + variant stream-error { + /// The last operation (a write or flush) failed before completion. + /// + /// More information is available in the `error` payload. + last-operation-failed(error), + /// The stream is closed: no more input will be accepted by the + /// stream. A closed output-stream will return this error on all + /// future operations. + closed + } + + /// An input bytestream. + /// + /// `input-stream`s are *non-blocking* to the extent practical on underlying + /// platforms. I/O operations always return promptly; if fewer bytes are + /// promptly available than requested, they return the number of bytes promptly + /// available, which could even be zero. To wait for data to be available, + /// use the `subscribe` function to obtain a `pollable` which can be polled + /// for using `wasi:io/poll`. + resource input-stream { + /// Perform a non-blocking read from the stream. + /// + /// This function returns a list of bytes containing the read data, + /// when successful. The returned list will contain up to `len` bytes; + /// it may return fewer than requested, but not more. The list is + /// empty when no bytes are available for reading at this time. The + /// pollable given by `subscribe` will be ready when more bytes are + /// available. + /// + /// This function fails with a `stream-error` when the operation + /// encounters an error, giving `last-operation-failed`, or when the + /// stream is closed, giving `closed`. + /// + /// When the caller gives a `len` of 0, it represents a request to + /// read 0 bytes. If the stream is still open, this call should + /// succeed and return an empty list, or otherwise fail with `closed`. + /// + /// The `len` parameter is a `u64`, which could represent a list of u8 which + /// is not possible to allocate in wasm32, or not desirable to allocate as + /// as a return value by the callee. The callee may return a list of bytes + /// less than `len` in size while more bytes are available for reading. + read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-error>; + + /// Read bytes from a stream, after blocking until at least one byte can + /// be read. Except for blocking, behavior is identical to `read`. + blocking-read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-error>; + + /// Skip bytes from a stream. Returns number of bytes skipped. + /// + /// Behaves identical to `read`, except instead of returning a list + /// of bytes, returns the number of bytes consumed from the stream. + skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result; + + /// Skip bytes from a stream, after blocking until at least one byte + /// can be skipped. Except for blocking behavior, identical to `skip`. + blocking-skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result; + + /// Create a `pollable` which will resolve once either the specified stream + /// has bytes available to read or the other end of the stream has been + /// closed. + /// The created `pollable` is a child resource of the `input-stream`. + /// Implementations may trap if the `input-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable; + } + + + /// An output bytestream. + /// + /// `output-stream`s are *non-blocking* to the extent practical on + /// underlying platforms. Except where specified otherwise, I/O operations also + /// always return promptly, after the number of bytes that can be written + /// promptly, which could even be zero. To wait for the stream to be ready to + /// accept data, the `subscribe` function to obtain a `pollable` which can be + /// polled for using `wasi:io/poll`. + resource output-stream { + /// Check readiness for writing. This function never blocks. + /// + /// Returns the number of bytes permitted for the next call to `write`, + /// or an error. Calling `write` with more bytes than this function has + /// permitted will trap. + /// + /// When this function returns 0 bytes, the `subscribe` pollable will + /// become ready when this function will report at least 1 byte, or an + /// error. + check-write: func() -> result; + + /// Perform a write. This function never blocks. + /// + /// Precondition: check-write gave permit of Ok(n) and contents has a + /// length of less than or equal to n. Otherwise, this function will trap. + /// + /// returns Err(closed) without writing if the stream has closed since + /// the last call to check-write provided a permit. + write: func( + contents: list + ) -> result<_, stream-error>; + + /// Perform a write of up to 4096 bytes, and then flush the stream. Block + /// until all of these operations are complete, or an error occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write`, and `flush`, and is implemented with the + /// following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while !contents.is_empty() { + /// // Wait for the stream to become writable + /// poll-one(pollable); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, contents.len()); + /// let (chunk, rest) = contents.split_at(len); + /// this.write(chunk ); // eliding error handling + /// contents = rest; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// poll-one(pollable); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-and-flush: func( + contents: list + ) -> result<_, stream-error>; + + /// Request to flush buffered output. This function never blocks. + /// + /// This tells the output-stream that the caller intends any buffered + /// output to be flushed. the output which is expected to be flushed + /// is all that has been passed to `write` prior to this call. + /// + /// Upon calling this function, the `output-stream` will not accept any + /// writes (`check-write` will return `ok(0)`) until the flush has + /// completed. The `subscribe` pollable will become ready when the + /// flush has completed and the stream can accept more writes. + flush: func() -> result<_, stream-error>; + + /// Request to flush buffered output, and block until flush completes + /// and stream is ready for writing again. + blocking-flush: func() -> result<_, stream-error>; + + /// Create a `pollable` which will resolve once the output-stream + /// is ready for more writing, or an error has occured. When this + /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an + /// error. + /// + /// If the stream is closed, this pollable is always ready immediately. + /// + /// The created `pollable` is a child resource of the `output-stream`. + /// Implementations may trap if the `output-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable; + + /// Write zeroes to a stream. + /// + /// this should be used precisely like `write` with the exact same + /// preconditions (must use check-write first), but instead of + /// passing a list of bytes, you simply pass the number of zero-bytes + /// that should be written. + write-zeroes: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, stream-error>; + + /// Perform a write of up to 4096 zeroes, and then flush the stream. + /// Block until all of these operations are complete, or an error + /// occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with + /// the following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while num_zeroes != 0 { + /// // Wait for the stream to become writable + /// poll-one(pollable); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, num_zeroes); + /// this.write-zeroes(len); // eliding error handling + /// num_zeroes -= len; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// poll-one(pollable); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-zeroes-and-flush: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, stream-error>; + + /// Read from one stream and write to another. + /// + /// The behavior of splice is equivelant to: + /// 1. calling `check-write` on the `output-stream` + /// 2. calling `read` on the `input-stream` with the smaller of the + /// `check-write` permitted length and the `len` provided to `splice` + /// 3. calling `write` on the `output-stream` with that read data. + /// + /// Any error reported by the call to `check-write`, `read`, or + /// `write` ends the splice and reports that error. + /// + /// This function returns the number of bytes transferred; it may be less + /// than `len`. + splice: func( + /// The stream to read from + src: borrow, + /// The number of bytes to splice + len: u64, + ) -> result; + + /// Read from one stream and write to another, with blocking. + /// + /// This is similar to `splice`, except that it blocks until the + /// `output-stream` is ready for writing, and the `input-stream` + /// is ready for reading, before performing the `splice`. + blocking-splice: func( + /// The stream to read from + src: borrow, + /// The number of bytes to splice + len: u64, + ) -> result; + } +} diff --git a/golem-rust/wit/deps/io/world.wit b/golem-rust/wit/deps/io/world.wit new file mode 100644 index 0000000..5f0b43f --- /dev/null +++ b/golem-rust/wit/deps/io/world.wit @@ -0,0 +1,6 @@ +package wasi:io@0.2.0; + +world imports { + import streams; + import poll; +} diff --git a/golem-rust/wit/deps/keyvalue/atomic.wit b/golem-rust/wit/deps/keyvalue/atomic.wit new file mode 100644 index 0000000..1d32b7e --- /dev/null +++ b/golem-rust/wit/deps/keyvalue/atomic.wit @@ -0,0 +1,31 @@ +/// A keyvalue interface that provides atomic operations. +/// +/// Atomic operations are single, indivisible operations. When a fault causes +/// an atomic operation to fail, it will appear to the invoker of the atomic +/// operation that the action either completed successfully or did nothing +/// at all. +interface atomic { + /// A keyvalue interface that provides atomic operations. + use types.{bucket, error, key}; + + /// Atomically increment the value associated with the key in the bucket by the + /// given delta. It returns the new value. + /// + /// If the key does not exist in the bucket, it creates a new key-value pair + /// with the value set to the given delta. + /// + /// If any other error occurs, it returns an `Err(error)`. + increment: func(bucket: borrow, key: key, delta: u64) -> result; + + /// Compare-and-swap (CAS) atomically updates the value associated with the key + /// in the bucket if the value matches the old value. This operation returns + /// `Ok(true)` if the swap was successful, `Ok(false)` if the value did not match, + /// + /// A successful CAS operation means the current value matched the `old` value + /// and was replaced with the `new` value. + /// + /// If the key does not exist in the bucket, it returns `Ok(false)`. + /// + /// If any other error occurs, it returns an `Err(error)`. + compare-and-swap: func(bucket: borrow, key: key, old: u64, new: u64) -> result; +} \ No newline at end of file diff --git a/golem-rust/wit/deps/keyvalue/caching.wit b/golem-rust/wit/deps/keyvalue/caching.wit new file mode 100644 index 0000000..8235527 --- /dev/null +++ b/golem-rust/wit/deps/keyvalue/caching.wit @@ -0,0 +1,98 @@ +// The `wasi:keyvalue/cache` interface defines the operations of a single +// instance of a "cache", which is a non-durable, weakly-consistent key-value +// store. "Non-durable" means that caches are allowed and expected to +// arbitrarily discard key-value entries. "Weakly-consistent" means that there +// are essentially no guarantees that operations will agree on their results: a +// get following a set may not observe the set value; multiple gets may observe +// different previous set values; etc. The only guarantee is that values are +// not materialized "out of thin air": if a `get` returns a value, that value +// was passed to a `set` operation at some point in time in the past. +// Additionally, caches MUST make a best effort to respect the supplied +// Time-to-Live values (within the usual limitations around time in a +// distributed setting). +interface cache { + use wasi:io/poll@0.2.0.{pollable}; + use types.{key, incoming-value, outgoing-value, error}; + + // The `get` operation returns the value passed by a previous `set` for the + // same key within the given TTL or none if there is no such value. + get: func(k: key) -> future-get-result; + + // This block defines a special resource type used by `get` to emulate + // `future,error>>`. In the return value + // of the `get` method, the outer `option` returns `none` when the pollable + // is not yet ready and the inner `option` returns `none` when the + // requested key wasn't present. + resource future-get-result { + future-get-result-get: func() -> option, error>>; + listen-to-future-get-result: func() -> pollable; + } + + // The `exists` operation returns whether a value was previously `set` for + // the given key within the TTL. + exists: func(k: key) -> future-exists-result; + + // This block defines a special resource type used by `exists` to emulate + // `future>`. + resource future-exists-result { + future-exists-result-get: func() -> option>; + listen-to-future-exists-result: func() -> pollable; + } + + // The `set` operation sets the given value for the given key for the given + // time-to-live (TTL) duration, if supplied, specified in milliseconds. If + // a TTL is not supplied, the key may be kept indefinitely (as-if a very + // large TTL were used). If the key is already present in the cache, the + // value is updated in-place. In the common case of computing and caching a + // value if the given key is not already in the cache, consider using + // `get-or-set` (below) intead of separate `get` and `set` operations. + set: func(k: key, v: borrow, TTL-ms: option) -> future-result; + + // This block defines a special resource type used by `set` and `delete` to + // emulate `future>`. + resource future-result { + future-result-get: func() -> option>; + listen-to-future-result: func() -> pollable; + } + + // The `get-or-set` operation asynchronously returns one of two cases + // enumerated by `get-or-set-entry`: in the `occupied` case, the given key + // already has a value present in the cache; in the `vacant` case, there + // was no value and the caller should write a value into the returned + // `vacancy`. This operation allows multiple concurrent `get-or-set` + // invocations to rendezvous such that only one invocation receives the + // `vacant` result while all other invocations wait until the vacancy is + // filled before receiving an `occupied` result. Implementations are not + // required to implement this rendezvous or to rendezvous in all possible + // cases. + variant get-or-set-entry { + occupied(incoming-value), + vacant(vacancy) + } + get-or-set: func(k: key) -> future-get-or-set-result; + + // This block defines a special resource type used by `get-or-set` to + // emulate `future>`. + resource future-get-or-set-result { + future-get-or-set-result-get: func() -> option>; + listen-to-future-get-or-set-result: func() -> pollable; + } + + // The following block defines the `vacancy` resource type. (When resource + // types are added, the `u32` type aliases can be replaced by proper + // `resource` types.) When the caller of `get-or-set` receives a `vacancy`, + // they must either call the `fill` method or drop the `vacancy` to + // indicate an error that prevents calling `fill`. An implementation MAY + // have a timeout that drops a vacancy that hasn't been filled in order + // to unblock other waiting `get-or-set` callers. + resource vacancy { + vacancy-fill: func(TTL-ms: option) -> outgoing-value; + } + + // The `delete` operation removes any value with the given key from the + // cache. Like all cache operations, `delete` is weakly ordered and thus + // concurrent `get` calls may still see deleted keys for a period of time. + // Additionally, due to weak ordering, concurrent `set` calls for the same + // key may or may not get deleted. + delete: func(k: key) -> future-result; +} \ No newline at end of file diff --git a/golem-rust/wit/deps/keyvalue/error.wit b/golem-rust/wit/deps/keyvalue/error.wit new file mode 100644 index 0000000..cd244f6 --- /dev/null +++ b/golem-rust/wit/deps/keyvalue/error.wit @@ -0,0 +1,20 @@ +interface wasi-keyvalue-error { + /// An error resource type for keyvalue operations. + /// + /// Common errors: + /// - Connectivity errors (e.g. network errors): when the client cannot establish + /// a connection to the keyvalue service. + /// - Authentication and Authorization errors: when the client fails to authenticate + /// or does not have the required permissions to perform the operation. + /// - Data errors: when the client sends incompatible or corrupted data. + /// - Resource errors: when the system runs out of resources (e.g. memory). + /// - Internal errors: unexpected errors on the server side. + /// + /// Currently, this provides only one function to return a string representation + /// of the error. In the future, this will be extended to provide more information + /// about the error. + // Soon: switch to `resource error { ... }` + resource error { + trace: func() -> string; + } +} \ No newline at end of file diff --git a/golem-rust/wit/deps/keyvalue/eventual-batch.wit b/golem-rust/wit/deps/keyvalue/eventual-batch.wit new file mode 100644 index 0000000..080999e --- /dev/null +++ b/golem-rust/wit/deps/keyvalue/eventual-batch.wit @@ -0,0 +1,81 @@ +/// A keyvalue interface that provides eventually consistent batch operations. +/// +/// A batch operation is an operation that operates on multiple keys at once. +/// +/// Batch operations are useful for reducing network round-trip time. For example, +/// if you want to get the values associated with 100 keys, you can either do 100 get +/// operations or you can do 1 batch get operation. The batch operation is +/// faster because it only needs to make 1 network call instead of 100. +/// +/// A batch operation does not guarantee atomicity, meaning that if the batch +/// operation fails, some of the keys may have been modified and some may not. +/// Transactional operations are being worked on and will be added in the future to +/// provide atomicity. +/// +/// Data consistency in a key value store refers to the gaurantee that once a +/// write operation completes, all subsequent read operations will return the +/// value that was written. +/// +/// The level of consistency in batch operations is **eventual consistency**, the same +/// with the readwrite interface. This interface does not guarantee strong consistency, +/// meaning that if a write operation completes, subsequent read operations may not return +/// the value that was written. +interface eventual-batch { + /// A keyvalue interface that provides batch get operations. + use types.{bucket, error, key, incoming-value, outgoing-value}; + + /// Get the values associated with the keys in the bucket. It returns a list of + /// incoming-value that can be consumed to get the value associated with the key. + /// + /// If any of the keys do not exist in the bucket, it returns a `none` value for + /// that key in the list. + /// + /// Note that the key-value pairs are guaranteed to be returned in the same order + /// + /// MAY show an out-of-date value if there are concurrent writes to the bucket. + /// + /// If any other error occurs, it returns an `Err(error)`. + get-many: func(bucket: borrow, keys: list) -> result>, error>; + + /// Get all the keys in the bucket. It returns a list of keys. + /// + /// Note that the keys are not guaranteed to be returned in any particular order. + /// + /// If the bucket is empty, it returns an empty list. + /// + /// MAY show an out-of-date list of keys if there are concurrent writes to the bucket. + /// + /// If any error occurs, it returns an `Err(error)`. + keys: func(bucket: borrow) -> result, error>; + + /// Set the values associated with the keys in the bucket. If the key already + /// exists in the bucket, it overwrites the value. + /// + /// Note that the key-value pairs are not guaranteed to be set in the order + /// they are provided. + /// + /// If any of the keys do not exist in the bucket, it creates a new key-value pair. + /// + /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it + /// does not rollback the key-value pairs that were already set. Thus, this batch operation + /// does not guarantee atomicity, implying that some key-value pairs could be + /// set while others might fail. + /// + /// Other concurrent operations may also be able to see the partial results. + set-many: func(bucket: borrow, key-values: list>>) -> result<_, error>; + + /// Delete the key-value pairs associated with the keys in the bucket. + /// + /// Note that the key-value pairs are not guaranteed to be deleted in the order + /// they are provided. + /// + /// If any of the keys do not exist in the bucket, it skips the key. + /// + /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it + /// does not rollback the key-value pairs that were already deleted. Thus, this batch operation + /// does not guarantee atomicity, implying that some key-value pairs could be + /// deleted while others might fail. + /// + /// Other concurrent operations may also be able to see the partial results. + delete-many: func(bucket: borrow, keys: list) -> result<_, error>; +} \ No newline at end of file diff --git a/golem-rust/wit/deps/keyvalue/eventual.wit b/golem-rust/wit/deps/keyvalue/eventual.wit new file mode 100644 index 0000000..e6e33cf --- /dev/null +++ b/golem-rust/wit/deps/keyvalue/eventual.wit @@ -0,0 +1,56 @@ +/// A keyvalue interface that provides eventually consistent CRUD operations. +/// +/// A CRUD operation is an operation that acts on a single key-value pair. +/// +/// The value in the key-value pair is defined as a `u8` byte array and the intention +/// is that it is the common denominator for all data types defined by different +/// key-value stores to handle data, ensuring compatibility between different +/// key-value stores. Note: the clients will be expecting serialization/deserialization overhead +/// to be handled by the key-value store. The value could be a serialized object from +/// JSON, HTML or vendor-specific data types like AWS S3 objects. +/// +/// Data consistency in a key value store refers to the gaurantee that once a +/// write operation completes, all subsequent read operations will return the +/// value that was written. +/// +/// The level of consistency in readwrite interfaces is **eventual consistency**, +/// which means that if a write operation completes successfully, all subsequent +/// read operations will eventually return the value that was written. In other words, +/// if we pause the updates to the system, the system eventually will return +/// the last updated value for read. +interface eventual { + /// A keyvalue interface that provides simple read and write operations. + use types.{bucket, error, incoming-value, key, outgoing-value}; + + /// Get the value associated with the key in the bucket. + /// + /// The value is returned as an option. If the key-value pair exists in the + /// bucket, it returns `Ok(value)`. If the key does not exist in the + /// bucket, it returns `Ok(none)`. + /// + /// If any other error occurs, it returns an `Err(error)`. + get: func(bucket: borrow, key: key) -> result, error>; + + /// Set the value associated with the key in the bucket. If the key already + /// exists in the bucket, it overwrites the value. + /// + /// If the key does not exist in the bucket, it creates a new key-value pair. + /// + /// If any other error occurs, it returns an `Err(error)`. + set: func(bucket: borrow, key: key, outgoing-value: borrow) -> result<_, error>; + + /// Delete the key-value pair associated with the key in the bucket. + /// + /// If the key does not exist in the bucket, it does nothing. + /// + /// If any other error occurs, it returns an `Err(error)`. + delete: func(bucket: borrow, key: key) -> result<_, error>; + + /// Check if the key exists in the bucket. + /// + /// If the key exists in the bucket, it returns `Ok(true)`. If the key does + /// not exist in the bucket, it returns `Ok(false)`. + /// + /// If any other error occurs, it returns an `Err(error)`. + exists: func(bucket: borrow, key: key) -> result; +} \ No newline at end of file diff --git a/golem-rust/wit/deps/keyvalue/handle-watch.wit b/golem-rust/wit/deps/keyvalue/handle-watch.wit new file mode 100644 index 0000000..0ca7b37 --- /dev/null +++ b/golem-rust/wit/deps/keyvalue/handle-watch.wit @@ -0,0 +1,17 @@ +/// A keyvalue interface that provides handle-watch operations. +/// +/// This interface is used to provide event-driven mechanisms to handle +/// keyvalue changes. +interface handle-watch { + /// A keyvalue interface that provides handle-watch operations. + use types.{bucket, key, incoming-value}; + + /// Handle the `set` event for the given bucket and key. + /// It returns a `incoming-value` that represents the new value being set. + /// The new value can be consumed by the handler. + on-set: func(bucket: bucket, key: key, incoming-value: borrow); + + /// Handle the `delete` event for the given bucket and key. + /// It returns a `key` that represents the key being deleted. + on-delete: func(bucket: bucket, key: key); +} \ No newline at end of file diff --git a/golem-rust/wit/deps/keyvalue/types.wit b/golem-rust/wit/deps/keyvalue/types.wit new file mode 100644 index 0000000..ba3416b --- /dev/null +++ b/golem-rust/wit/deps/keyvalue/types.wit @@ -0,0 +1,72 @@ +// A generic keyvalue interface for WASI. +interface types { + /// A bucket is a collection of key-value pairs. Each key-value pair is stored + /// as a entry in the bucket, and the bucket itself acts as a collection of all + /// these entries. + /// + /// It is worth noting that the exact terminology for bucket in key-value stores + /// can very depending on the specific implementation. For example, + /// 1. Amazon DynamoDB calls a collection of key-value pairs a table + /// 2. Redis has hashes, sets, and sorted sets as different types of collections + /// 3. Cassandra calls a collection of key-value pairs a column family + /// 4. MongoDB calls a collection of key-value pairs a collection + /// 5. Riak calls a collection of key-value pairs a bucket + /// 6. Memcached calls a collection of key-value pairs a slab + /// 7. Azure Cosmos DB calls a collection of key-value pairs a container + /// + /// In this interface, we use the term `bucket` to refer to a collection of key-value + // Soon: switch to `resource bucket { ... }` + resource bucket { + /// Opens a bucket with the given name. + /// + /// If any error occurs, including if the bucket does not exist, it returns an `Err(error)`. + open-bucket: static func(name: string) -> result; + } + /// A key is a unique identifier for a value in a bucket. The key is used to + /// retrieve the value from the bucket. + type key = string; + + use wasi:io/streams@0.2.0.{input-stream, output-stream}; + use wasi-keyvalue-error.{ error }; + /// A value is the data stored in a key-value pair. The value can be of any type + /// that can be represented in a byte array. It provides a way to write the value + /// to the output-stream defined in the `wasi-io` interface. + // Soon: switch to `resource value { ... }` + resource outgoing-value { + new-outgoing-value: static func() -> outgoing-value; + /// Writes the value to the output-stream asynchronously. + /// If any other error occurs, it returns an `Err(error)`. + outgoing-value-write-body-async: func() -> result; + /// Writes the value to the output-stream synchronously. + /// If any other error occurs, it returns an `Err(error)`. + outgoing-value-write-body-sync: func(value: outgoing-value-body-sync) -> result<_, error>; + } + type outgoing-value-body-async = output-stream; + type outgoing-value-body-sync = list; + + /// A incoming-value is a wrapper around a value. It provides a way to read the value + /// from the `input-stream` defined in the `wasi-io` interface. + /// + /// The incoming-value provides two ways to consume the value: + /// 1. `incoming-value-consume-sync` consumes the value synchronously and returns the + /// value as a `list`. + /// 2. `incoming-value-consume-async` consumes the value asynchronously and returns the + /// value as an `input-stream`. + /// In addition, it provides a `incoming-value-size` function to get the size of the value. + /// This is useful when the value is large and the caller wants to allocate a buffer of + /// the right size to consume the value. + // Soon: switch to `resource incoming-value { ... }` + resource incoming-value { + /// Consumes the value synchronously and returns the value as a list of bytes. + /// If any other error occurs, it returns an `Err(error)`. + incoming-value-consume-sync: func() -> result; + /// Consumes the value asynchronously and returns the value as an `input-stream`. + /// If any other error occurs, it returns an `Err(error)`. + incoming-value-consume-async: func() -> result; + /// The size of the value in bytes. + /// If the size is unknown or unavailable, this function returns an `Err(error)`. + incoming-value-size: func() -> result; + } + type incoming-value-async-body = input-stream; + type incoming-value-sync-body = list; +} \ No newline at end of file diff --git a/golem-rust/wit/deps/keyvalue/world.wit b/golem-rust/wit/deps/keyvalue/world.wit new file mode 100644 index 0000000..ea64fe5 --- /dev/null +++ b/golem-rust/wit/deps/keyvalue/world.wit @@ -0,0 +1,26 @@ +package wasi:keyvalue@0.1.0; + +/// The `wasi:keyvalue/imports` world provides common APIs for interacting +/// with key-value stores. Components targeting this world will be able to +/// do +/// 1. CRUD (create, read, update, delete) operations on key-value stores. +/// 2. Atomic `increment` and CAS (compare-and-swap) operations. +/// 3. Batch operations that can reduce the number of round trips to the network. +world imports { + /// The `eventual` capability allows the component to perform + /// eventually consistent CRUD operations on the key-value store. + import eventual; + + /// The `atomic` capability allows the component to perform atomic + /// `increment` and CAS (compare-and-swap) operations. + import atomic; + + /// The `eventual-batch` capability allows the component to perform eventually + /// consistent batch operations that can reduce the number of round trips to the network. + import eventual-batch; +} + +world keyvalue-handle-watch { + include imports; + export handle-watch; +} \ No newline at end of file diff --git a/golem-rust/wit/deps/logging/logging.wit b/golem-rust/wit/deps/logging/logging.wit new file mode 100644 index 0000000..adb5ee5 --- /dev/null +++ b/golem-rust/wit/deps/logging/logging.wit @@ -0,0 +1,37 @@ +package wasi:logging; + +/// WASI Logging is a logging API intended to let users emit log messages with +/// simple priority levels and context values. +interface logging { + /// A log level, describing a kind of message. + enum level { + /// Describes messages about the values of variables and the flow of + /// control within a program. + trace, + + /// Describes messages likely to be of interest to someone debugging a + /// program. + debug, + + /// Describes messages likely to be of interest to someone monitoring a + /// program. + info, + + /// Describes messages indicating hazardous situations. + warn, + + /// Describes messages indicating serious errors. + error, + + /// Describes messages indicating fatal errors. + critical, + } + + /// Emit a log message. + /// + /// A log message has a `level` describing what kind of message is being + /// sent, a context, which is an uninterpreted string meant to help + /// consumers group similar messages, and a string containing the message + /// text. + log: func(level: level, context: string, message: string); +} \ No newline at end of file diff --git a/golem-rust/wit/deps/random/insecure-seed.wit b/golem-rust/wit/deps/random/insecure-seed.wit new file mode 100644 index 0000000..47210ac --- /dev/null +++ b/golem-rust/wit/deps/random/insecure-seed.wit @@ -0,0 +1,25 @@ +package wasi:random@0.2.0; +/// The insecure-seed interface for seeding hash-map DoS resistance. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface insecure-seed { + /// Return a 128-bit value that may contain a pseudo-random value. + /// + /// The returned value is not required to be computed from a CSPRNG, and may + /// even be entirely deterministic. Host implementations are encouraged to + /// provide pseudo-random values to any program exposed to + /// attacker-controlled content, to enable DoS protection built into many + /// languages' hash-map implementations. + /// + /// This function is intended to only be called once, by a source language + /// to initialize Denial Of Service (DoS) protection in its hash-map + /// implementation. + /// + /// # Expected future evolution + /// + /// This will likely be changed to a value import, to prevent it from being + /// called multiple times and potentially used for purposes other than DoS + /// protection. + insecure-seed: func() -> tuple; +} diff --git a/golem-rust/wit/deps/random/insecure.wit b/golem-rust/wit/deps/random/insecure.wit new file mode 100644 index 0000000..c58f4ee --- /dev/null +++ b/golem-rust/wit/deps/random/insecure.wit @@ -0,0 +1,22 @@ +package wasi:random@0.2.0; +/// The insecure interface for insecure pseudo-random numbers. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface insecure { + /// Return `len` insecure pseudo-random bytes. + /// + /// This function is not cryptographically secure. Do not use it for + /// anything related to security. + /// + /// There are no requirements on the values of the returned bytes, however + /// implementations are encouraged to return evenly distributed values with + /// a long period. + get-insecure-random-bytes: func(len: u64) -> list; + + /// Return an insecure pseudo-random `u64` value. + /// + /// This function returns the same type of pseudo-random data as + /// `get-insecure-random-bytes`, represented as a `u64`. + get-insecure-random-u64: func() -> u64; +} diff --git a/golem-rust/wit/deps/random/random.wit b/golem-rust/wit/deps/random/random.wit new file mode 100644 index 0000000..0c017f0 --- /dev/null +++ b/golem-rust/wit/deps/random/random.wit @@ -0,0 +1,26 @@ +package wasi:random@0.2.0; +/// WASI Random is a random data API. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface random { + /// Return `len` cryptographically-secure random or pseudo-random bytes. + /// + /// This function must produce data at least as cryptographically secure and + /// fast as an adequately seeded cryptographically-secure pseudo-random + /// number generator (CSPRNG). It must not block, from the perspective of + /// the calling program, under any circumstances, including on the first + /// request and on requests for numbers of bytes. The returned data must + /// always be unpredictable. + /// + /// This function must always return fresh data. Deterministic environments + /// must omit this function, rather than implementing it with deterministic + /// data. + get-random-bytes: func(len: u64) -> list; + + /// Return a cryptographically-secure random or pseudo-random `u64` value. + /// + /// This function returns the same type of data as `get-random-bytes`, + /// represented as a `u64`. + get-random-u64: func() -> u64; +} diff --git a/golem-rust/wit/deps/random/world.wit b/golem-rust/wit/deps/random/world.wit new file mode 100644 index 0000000..3da3491 --- /dev/null +++ b/golem-rust/wit/deps/random/world.wit @@ -0,0 +1,7 @@ +package wasi:random@0.2.0; + +world imports { + import random; + import insecure; + import insecure-seed; +} diff --git a/golem-rust/wit/deps/sockets/instance-network.wit b/golem-rust/wit/deps/sockets/instance-network.wit new file mode 100644 index 0000000..e455d0f --- /dev/null +++ b/golem-rust/wit/deps/sockets/instance-network.wit @@ -0,0 +1,9 @@ + +/// This interface provides a value-export of the default network handle.. +interface instance-network { + use network.{network}; + + /// Get a handle to the default network. + instance-network: func() -> network; + +} diff --git a/golem-rust/wit/deps/sockets/ip-name-lookup.wit b/golem-rust/wit/deps/sockets/ip-name-lookup.wit new file mode 100644 index 0000000..8e639ec --- /dev/null +++ b/golem-rust/wit/deps/sockets/ip-name-lookup.wit @@ -0,0 +1,51 @@ + +interface ip-name-lookup { + use wasi:io/poll@0.2.0.{pollable}; + use network.{network, error-code, ip-address}; + + + /// Resolve an internet host name to a list of IP addresses. + /// + /// Unicode domain names are automatically converted to ASCII using IDNA encoding. + /// If the input is an IP address string, the address is parsed and returned + /// as-is without making any external requests. + /// + /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. + /// + /// This function never blocks. It either immediately fails or immediately + /// returns successfully with a `resolve-address-stream` that can be used + /// to (asynchronously) fetch the results. + /// + /// # Typical errors + /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. + /// + /// # References: + /// - + /// - + /// - + /// - + resolve-addresses: func(network: borrow, name: string) -> result; + + resource resolve-address-stream { + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + /// + /// # Typical errors + /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) + /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) + /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) + /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + resolve-next-address: func() -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } +} diff --git a/golem-rust/wit/deps/sockets/network.wit b/golem-rust/wit/deps/sockets/network.wit new file mode 100644 index 0000000..9cadf06 --- /dev/null +++ b/golem-rust/wit/deps/sockets/network.wit @@ -0,0 +1,145 @@ + +interface network { + /// An opaque resource that represents access to (a subset of) the network. + /// This enables context-based security for networking. + /// There is no need for this to map 1:1 to a physical network interface. + resource network; + + /// Error codes. + /// + /// In theory, every API can return any error code. + /// In practice, API's typically only return the errors documented per API + /// combined with a couple of errors that are always possible: + /// - `unknown` + /// - `access-denied` + /// - `not-supported` + /// - `out-of-memory` + /// - `concurrency-conflict` + /// + /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. + enum error-code { + /// Unknown error + unknown, + + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, + + /// The operation is not supported. + /// + /// POSIX equivalent: EOPNOTSUPP + not-supported, + + /// One of the arguments is invalid. + /// + /// POSIX equivalent: EINVAL + invalid-argument, + + /// Not enough memory to complete the operation. + /// + /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + out-of-memory, + + /// The operation timed out before it could finish completely. + timeout, + + /// This operation is incompatible with another asynchronous operation that is already in progress. + /// + /// POSIX equivalent: EALREADY + concurrency-conflict, + + /// Trying to finish an asynchronous operation that: + /// - has not been started yet, or: + /// - was already finished by a previous `finish-*` call. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + not-in-progress, + + /// The operation has been aborted because it could not be completed immediately. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + would-block, + + + /// The operation is not valid in the socket's current state. + invalid-state, + + /// A new socket resource could not be created because of a system limit. + new-socket-limit, + + /// A bind operation failed because the provided address is not an address that the `network` can bind to. + address-not-bindable, + + /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. + address-in-use, + + /// The remote address is not reachable + remote-unreachable, + + + /// The TCP connection was forcefully rejected + connection-refused, + + /// The TCP connection was reset. + connection-reset, + + /// A TCP connection was aborted. + connection-aborted, + + + /// The size of a datagram sent to a UDP socket exceeded the maximum + /// supported size. + datagram-too-large, + + + /// Name does not exist or has no suitable associated IP addresses. + name-unresolvable, + + /// A temporary failure in name resolution occurred. + temporary-resolver-failure, + + /// A permanent failure in name resolution occurred. + permanent-resolver-failure, + } + + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + + /// Similar to `AF_INET6` in POSIX. + ipv6, + } + + type ipv4-address = tuple; + type ipv6-address = tuple; + + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + record ipv4-socket-address { + /// sin_port + port: u16, + /// sin_addr + address: ipv4-address, + } + + record ipv6-socket-address { + /// sin6_port + port: u16, + /// sin6_flowinfo + flow-info: u32, + /// sin6_addr + address: ipv6-address, + /// sin6_scope_id + scope-id: u32, + } + + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + +} diff --git a/golem-rust/wit/deps/sockets/tcp-create-socket.wit b/golem-rust/wit/deps/sockets/tcp-create-socket.wit new file mode 100644 index 0000000..c7ddf1f --- /dev/null +++ b/golem-rust/wit/deps/sockets/tcp-create-socket.wit @@ -0,0 +1,27 @@ + +interface tcp-create-socket { + use network.{network, error-code, ip-address-family}; + use tcp.{tcp-socket}; + + /// Create a new TCP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. + /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` + /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + create-tcp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/golem-rust/wit/deps/sockets/tcp.wit b/golem-rust/wit/deps/sockets/tcp.wit new file mode 100644 index 0000000..fbe023b --- /dev/null +++ b/golem-rust/wit/deps/sockets/tcp.wit @@ -0,0 +1,309 @@ + +interface tcp { + use wasi:io/streams@0.2.0.{input-stream, output-stream}; + use wasi:io/poll@0.2.0.{pollable}; + use wasi:clocks/monotonic-clock@0.2.0.{duration}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + enum shutdown-type { + /// Similar to `SHUT_RD` in POSIX. + receive, + + /// Similar to `SHUT_WR` in POSIX. + send, + + /// Similar to `SHUT_RDWR` in POSIX. + both, + } + + + /// A TCP socket handle. + resource tcp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) + /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// + /// # Typical `finish` errors + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT + /// state of a recently closed socket on the same local address. In practice this means that the SO_REUSEADDR + /// socket option should be set implicitly on all platforms, except on Windows where this is the default behavior + /// and SO_REUSEADDR performs something different entirely. + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + + /// Connect to a remote endpoint. + /// + /// On success: + /// - the socket is transitioned into the Connection state + /// - a pair of streams is returned that can be used to read & write to the connection + /// + /// After a failed connection attempt, the only valid action left is to + /// `drop` the socket. A single socket can not be used to connect more than once. + /// + /// # Typical `start` errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) + /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `invalid-state`: The socket is already in the Connection state. (EISCONN) + /// - `invalid-state`: The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows) + /// + /// # Typical `finish` errors + /// - `timeout`: Connection timed out. (ETIMEDOUT) + /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A `connect` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; + finish-connect: func() -> result, error-code>; + + /// Start listening for new connections. + /// + /// Transitions the socket into the Listener state. + /// + /// Unlike POSIX: + /// - this function is async. This enables interactive WASI hosts to inject permission prompts. + /// - the socket must already be explicitly bound. + /// + /// # Typical `start` errors + /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) + /// - `invalid-state`: The socket is already in the Connection state. (EISCONN, EINVAL on BSD) + /// - `invalid-state`: The socket is already in the Listener state. + /// + /// # Typical `finish` errors + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// - `not-in-progress`: A `listen` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-listen: func() -> result<_, error-code>; + finish-listen: func() -> result<_, error-code>; + + /// Accept a new client socket. + /// + /// The returned socket is bound and in the Connection state. The following properties are inherited from the listener socket: + /// - `address-family` + /// - `keep-alive-enabled` + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// - `hop-limit` + /// - `receive-buffer-size` + /// - `send-buffer-size` + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// # Typical errors + /// - `invalid-state`: Socket is not in the Listener state. (EINVAL) + /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) + /// - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + accept: func() -> result, error-code>; + + /// Get the bound local address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; + + /// Get the remote address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; + + /// Whether the socket is listening for new connections. + /// + /// Equivalent to the SO_ACCEPTCONN socket option. + is-listening: func() -> bool; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; + + /// Hints the desired listen queue size. Implementations are free to ignore this. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// + /// # Typical errors + /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. + /// - `invalid-argument`: (set) The provided value was 0. + /// - `invalid-state`: (set) The socket is already in the Connection state. + set-listen-backlog-size: func(value: u64) -> result<_, error-code>; + + /// Enables or disables keepalive. + /// + /// The keepalive behavior can be adjusted using: + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true. + /// + /// Equivalent to the SO_KEEPALIVE socket option. + keep-alive-enabled: func() -> result; + set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; + + /// Amount of time the connection has to be idle before TCP starts sending keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-idle-time: func() -> result; + set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; + + /// The time between keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPINTVL socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-interval: func() -> result; + set-keep-alive-interval: func(value: duration) -> result<_, error-code>; + + /// The maximum amount of keepalive packets TCP should send before aborting the connection. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPCNT socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-count: func() -> result; + set-keep-alive-count: func(value: u32) -> result<_, error-code>; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + /// - `invalid-state`: (set) The socket is already in the Connection state. + /// - `invalid-state`: (set) The socket is already in the Listener state. + hop-limit: func() -> result; + set-hop-limit: func(value: u8) -> result<_, error-code>; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + /// - `invalid-state`: (set) The socket is already in the Connection state. + /// - `invalid-state`: (set) The socket is already in the Listener state. + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + + /// Initiate a graceful shutdown. + /// + /// - `receive`: The socket is not expecting to receive any data from + /// the peer. The `input-stream` associated with this socket will be + /// closed. Any data still in the receive queue at time of calling + /// this method will be discarded. + /// - `send`: The socket has no more data to send to the peer. The `output-stream` + /// associated with this socket will be closed and a FIN packet will be sent. + /// - `both`: Same effect as `receive` & `send` combined. + /// + /// This function is idempotent. Shutting a down a direction more than once + /// has no effect and returns `ok`. + /// + /// The shutdown function does not close (drop) the socket. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not in the Connection state. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; + } +} diff --git a/golem-rust/wit/deps/sockets/udp-create-socket.wit b/golem-rust/wit/deps/sockets/udp-create-socket.wit new file mode 100644 index 0000000..0482d1f --- /dev/null +++ b/golem-rust/wit/deps/sockets/udp-create-socket.wit @@ -0,0 +1,27 @@ + +interface udp-create-socket { + use network.{network, error-code, ip-address-family}; + use udp.{udp-socket}; + + /// Create a new UDP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. + /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, + /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References: + /// - + /// - + /// - + /// - + create-udp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/golem-rust/wit/deps/sockets/udp.wit b/golem-rust/wit/deps/sockets/udp.wit new file mode 100644 index 0000000..b9826a6 --- /dev/null +++ b/golem-rust/wit/deps/sockets/udp.wit @@ -0,0 +1,264 @@ + +interface udp { + use wasi:io/poll@0.2.0.{pollable}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + /// A received datagram. + record incoming-datagram { + /// The payload. + /// + /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + data: list, + + /// The source address. + /// + /// This field is guaranteed to match the remote address the stream was initialized with, if any. + /// + /// Equivalent to the `src_addr` out parameter of `recvfrom`. + remote-address: ip-socket-address, + } + + /// A datagram to be sent out. + record outgoing-datagram { + /// The payload. + data: list, + + /// The destination address. + /// + /// The requirements on this field depend on how the stream was initialized: + /// - with a remote address: this field must be None or match the stream's remote address exactly. + /// - without a remote address: this field is required. + /// + /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise it is equivalent to `sendto`. + remote-address: option, + } + + + + /// A UDP socket handle. + resource udp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the port is zero, the socket will be bound to a random free port. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// + /// # Typical `finish` errors + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + + /// Set up inbound & outbound communication channels, optionally to a specific peer. + /// + /// This function only changes the local socket configuration and does not generate any network traffic. + /// On success, the `remote-address` of the socket is updated. The `local-address` may be updated as well, + /// based on the best network path to `remote-address`. + /// + /// When a `remote-address` is provided, the returned streams are limited to communicating with that specific peer: + /// - `send` can only be used to send to this destination. + /// - `receive` will only return datagrams sent from the provided `remote-address`. + /// + /// This method may be called multiple times on the same socket to change its association, but + /// only the most recently returned pair of streams will be operational. Implementations may trap if + /// the streams returned by a previous invocation haven't been dropped yet before calling `stream` again. + /// + /// The POSIX equivalent in pseudo-code is: + /// ```text + /// if (was previously connected) { + /// connect(s, AF_UNSPEC) + /// } + /// if (remote_address is Some) { + /// connect(s, remote_address) + /// } + /// ``` + /// + /// Unlike in POSIX, the socket must already be explicitly bound. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-state`: The socket is not bound. + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + %stream: func(remote-address: option) -> result, error-code>; + + /// Get the current bound address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; + + /// Get the address the socket is currently streaming to. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + unicast-hop-limit: func() -> result; + set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + + resource incoming-datagram-stream { + /// Receive messages on the socket. + /// + /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. + /// The returned list may contain fewer elements than requested, but never more. + /// + /// This function returns successfully with an empty list when either: + /// - `max-results` is 0, or: + /// - `max-results` is greater than 0, but no results are immediately available. + /// This function never returns `error(would-block)`. + /// + /// # Typical errors + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + receive: func(max-results: u64) -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready to receive again. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + + resource outgoing-datagram-stream { + /// Check readiness for sending. This function never blocks. + /// + /// Returns the number of datagrams permitted for the next call to `send`, + /// or an error. Calling `send` with more datagrams than this function has + /// permitted will trap. + /// + /// When this function returns ok(0), the `subscribe` pollable will + /// become ready when this function will report at least ok(1), or an + /// error. + /// + /// Never returns `would-block`. + check-send: func() -> result; + + /// Send messages on the socket. + /// + /// This function attempts to send all provided `datagrams` on the socket without blocking and + /// returns how many messages were actually sent (or queued for sending). This function never + /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` is returned. + /// + /// This function semantically behaves the same as iterating the `datagrams` list and sequentially + /// sending each individual datagram until either the end of the list has been reached or the first error occurred. + /// If at least one datagram has been sent successfully, this function never returns an error. + /// + /// If the input list is empty, the function returns `ok(0)`. + /// + /// Each call to `send` must be permitted by a preceding `check-send`. Implementations must trap if + /// either `check-send` was not called or `datagrams` contains more items than `check-send` permitted. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `stream`. (EISCONN) + /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + send: func(datagrams: list) -> result; + + /// Create a `pollable` which will resolve once the stream is ready to send again. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } +} diff --git a/golem-rust/wit/deps/sockets/world.wit b/golem-rust/wit/deps/sockets/world.wit new file mode 100644 index 0000000..f8bb92a --- /dev/null +++ b/golem-rust/wit/deps/sockets/world.wit @@ -0,0 +1,11 @@ +package wasi:sockets@0.2.0; + +world imports { + import instance-network; + import network; + import udp; + import udp-create-socket; + import tcp; + import tcp-create-socket; + import ip-name-lookup; +} diff --git a/golem-rust/wit/deps/wasm-rpc/wasm-rpc.wit b/golem-rust/wit/deps/wasm-rpc/wasm-rpc.wit new file mode 100644 index 0000000..f7deff5 --- /dev/null +++ b/golem-rust/wit/deps/wasm-rpc/wasm-rpc.wit @@ -0,0 +1,54 @@ +package golem:rpc@0.1.0; + +interface types { + type node-index = s32; + + record wit-value { + nodes: list, + } + + variant wit-node { + record-value(list), + variant-value(tuple>), + enum-value(u32), + flags-value(list), + tuple-value(list), + list-value(list), + option-value(option), + result-value(result, option>), + prim-u8(u8), + prim-u16(u16), + prim-u32(u32), + prim-u64(u64), + prim-s8(s8), + prim-s16(s16), + prim-s32(s32), + prim-s64(s64), + prim-float32(float32), + prim-float64(float64), + prim-char(char), + prim-bool(bool), + prim-string(string), + } + + record uri { + value: string, + } + + variant rpc-error { + protocol-error(string), + denied(string), + not-found(string), + remote-internal-error(string) + } + + resource wasm-rpc { + constructor(location: uri); + + invoke-and-await: func(function-name: string, function-params: list) -> result; + } +} + +world wit-value { + import types; +} diff --git a/golem-rust/wit/golem-rust.wit b/golem-rust/wit/golem-rust.wit new file mode 100644 index 0000000..c00af7d --- /dev/null +++ b/golem-rust/wit/golem-rust.wit @@ -0,0 +1,5 @@ +package golem:rust; + +world golem-rust { + import golem:api/host@0.2.0; +}