├── examples └── mqtt-app │ ├── .gitignore │ ├── Cargo.toml │ ├── src │ └── lib.rs │ ├── spin.toml │ └── Cargo.lock ├── spin-pluginify.toml ├── .vscode └── settings.json ├── docs └── plugin-development.md ├── .gitignore ├── sdk ├── Cargo.toml ├── macro │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── src │ └── lib.rs ├── templates └── mqtt-rust │ ├── content │ ├── Cargo.toml.tmpl │ ├── src │ │ └── lib.rs │ └── spin.toml │ └── metadata │ └── spin-template.toml ├── .github ├── CODEOWNERS └── workflows │ └── build.yaml ├── .devcontainer ├── bootstrap.sh └── devcontainer.json ├── src ├── main.rs └── lib.rs ├── spin-mqtt.wit ├── LICENSE ├── Makefile ├── Cargo.toml ├── release-process.md ├── README.md └── tests └── integration_test.sh /examples/mqtt-app/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .spin/ 3 | -------------------------------------------------------------------------------- /spin-pluginify.toml: -------------------------------------------------------------------------------- 1 | name = "trigger-mqtt" 2 | description = "A Spin trigger for MQTT events" 3 | version = "0.6.0" 4 | spin_compatibility = ">=2.0" 5 | license = "MIT" 6 | package = "./target/release/trigger-mqtt" -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "bindgen", 4 | "czvf", 5 | "Fermyon", 6 | "mqtt", 7 | "Paho", 8 | "wasmtime" 9 | ], 10 | "rust-analyzer.linkedProjects": [ 11 | "./Cargo.toml", 12 | "./examples/mqtt-app/Cargo.toml", 13 | ] 14 | } -------------------------------------------------------------------------------- /docs/plugin-development.md: -------------------------------------------------------------------------------- 1 | # Plugin Development 2 | 3 | ## Spin Plugin Authoring 4 | 5 | 6 | 7 | ## Updating Plugin 8 | 9 | Spin plugin index is maintained here which needs updating, if new version is released. 10 | https://github.com/spinframework/spin-plugins/tree/main/manifests 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | # Artifacts generated by pluginify 13 | trigger-mqtt-*.tar.gz 14 | trigger-mqtt.json -------------------------------------------------------------------------------- /sdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spin-mqtt-sdk" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | rust-version = { workspace = true } 6 | include = ["../spin-mqtt.wit"] 7 | 8 | [lib] 9 | name = "spin_mqtt_sdk" 10 | 11 | [dependencies] 12 | spin-executor = "5.1.0" 13 | spin-mqtt-macro = { path = "macro" } 14 | wit-bindgen = { workspace = true } -------------------------------------------------------------------------------- /examples/mqtt-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mqtt-app" 3 | authors = ["Suneet Nangia "] 4 | description = "Demo app to send and receive MQTT messages." 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = [ "cdylib" ] 10 | 11 | [dependencies] 12 | anyhow = "1" 13 | chrono = "*" 14 | spin-mqtt-sdk = { path = "../../sdk" } 15 | 16 | 17 | [workspace] 18 | -------------------------------------------------------------------------------- /templates/mqtt-rust/content/Cargo.toml.tmpl: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "{{project-name | kebab_case}}" 3 | authors = ["{{authors}}"] 4 | description = "{{project-description}}" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = [ "cdylib" ] 10 | 11 | [dependencies] 12 | anyhow = "1" 13 | chrono = "*" 14 | spin-mqtt-sdk = { git = "https://github.com/spinframework/spin-trigger-mqtt"} 15 | 16 | [workspace] -------------------------------------------------------------------------------- /sdk/macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spin-mqtt-macro" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | rust-version = { workspace = true } 6 | include = ["../../spin-mqtt.wit"] 7 | 8 | [lib] 9 | name = "spin_mqtt_macro" 10 | proc-macro = true 11 | 12 | [dependencies] 13 | proc-macro2 = "1" 14 | quote = "1.0" 15 | syn = { version = "1.0", features = ["full"] } 16 | wit-bindgen = { workspace = true } -------------------------------------------------------------------------------- /sdk/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use spin_mqtt_macro::mqtt_component; 2 | 3 | #[doc(hidden)] 4 | pub use spin_executor as executor; 5 | 6 | #[doc(hidden)] 7 | pub mod wit { 8 | #![allow(missing_docs)] 9 | 10 | wit_bindgen::generate!({ 11 | world: "spin-mqtt-sdk", 12 | path: "..", 13 | }); 14 | } 15 | 16 | #[doc(hidden)] 17 | pub use wit_bindgen; 18 | 19 | #[doc(inline)] 20 | pub use wit::spin::mqtt_trigger::spin_mqtt_types::{Error, Metadata, Payload}; 21 | -------------------------------------------------------------------------------- /templates/mqtt-rust/content/src/lib.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use spin_mqtt_sdk::{mqtt_component, Metadata, Payload}; 3 | 4 | #[mqtt_component] 5 | async fn handle_message(message: Payload, metadata: Metadata) -> anyhow::Result<()> { 6 | let datetime: DateTime = std::time::SystemTime::now().into(); 7 | let formatted_time = datetime.format("%Y-%m-%d %H:%M:%S.%f").to_string(); 8 | 9 | println!( 10 | "{:?} Message received by wasm component: '{}'", 11 | formatted_time, 12 | String::from_utf8_lossy(&message) 13 | ); 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /examples/mqtt-app/src/lib.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use spin_mqtt_sdk::{mqtt_component, Metadata, Payload}; 3 | 4 | #[mqtt_component] 5 | async fn handle_message(message: Payload, metadata: Metadata) -> anyhow::Result<()> { 6 | let datetime: DateTime = std::time::SystemTime::now().into(); 7 | let formatted_time = datetime.format("%Y-%m-%d %H:%M:%S.%f").to_string(); 8 | 9 | println!( 10 | "{:?} Message received by wasm component: '{}' on topic '{}'", 11 | formatted_time, 12 | String::from_utf8_lossy(&message), 13 | metadata.topic 14 | ); 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS 2 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 3 | 4 | # NOTE: Order is important; the last matching pattern takes the most precedence. When someone opens a pull request that 5 | # only modifies files under a certain matching pattern, only those code owners will be requested for a review. 6 | 7 | # These owners will be the default owners for everything in the repository. Unless a later match takes precedence, they 8 | # will be requested for review when someone opens a pull request. 9 | * @suneetnangia @radu-matei @karthik2804 @fibonacci1729 10 | -------------------------------------------------------------------------------- /templates/mqtt-rust/content/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "{{project-name | kebab_case}}" 5 | version = "0.1.0" 6 | authors = ["{{authors}}"] 7 | description = "{{project-description}}" 8 | 9 | [application.trigger.mqtt] 10 | address = "{{mqtt-address}}" 11 | username = "{{mqtt-username}}" 12 | password = "{{mqtt-password}}" 13 | keep_alive_interval = "{{mqtt-keep_alive_interval}}" 14 | 15 | [[trigger.mqtt]] 16 | component = "{{project-name | kebab_case}}" 17 | topic = "{{mqtt-topic}}" 18 | qos = "{{mqtt-qos}}" 19 | 20 | [component.{{project-name | kebab_case}}] 21 | source = "target/wasm32-wasip2/release/{{project-name | snake_case}}.wasm" 22 | allowed_outbound_hosts = ["{{mqtt-address}}"] 23 | [component.{{project-name | kebab_case}}.build] 24 | command = "cargo build --target wasm32-wasip2 --release" -------------------------------------------------------------------------------- /.devcontainer/bootstrap.sh: -------------------------------------------------------------------------------- 1 | # Updates latest stable toolchain for Rust and clippy/fmt for this toolchain 2 | rustup update stable && rustup default stable && rustup component add clippy rustfmt 3 | 4 | # Installs wasm32 compiler targets 5 | rustup target add wasm32-wasip2 wasm32-unknown-unknown 6 | 7 | # Installs cmake 8 | sudo apt update && sudo apt install cmake -y 9 | 10 | # Install Spin 11 | if [ -d "spininstall" ]; then 12 | echo "Deleting existing spininstall directory..." 13 | rm -fr spininstall 14 | fi 15 | 16 | mkdir spininstall && cd spininstall 17 | curl -fsSL https://spinframework.dev/downloads/install.sh | bash 18 | sudo mv spin /usr/local/bin/ 19 | cd ../ && rm -fr spininstall 20 | 21 | # Install MQTTX client for testing purposes 22 | curl -LO https://www.emqx.com/en/downloads/MQTTX/v1.9.9/mqttx-cli-linux-x64 23 | sudo install ./mqttx-cli-linux-x64 /usr/local/bin/mqttx 24 | rm ./mqttx-cli-linux-x64 -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use spin_runtime_factors::FactorsBuilder; 3 | use spin_trigger::cli::FactorsTriggerCommand; 4 | use trigger_mqtt::MqttTrigger; 5 | 6 | type Command = FactorsTriggerCommand; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<(), anyhow::Error> { 10 | spin_telemetry::init(build_info())?; 11 | let trigger = Command::parse(); 12 | trigger.run().await 13 | } 14 | 15 | /// Returns build information of the parent Spin process, similar to: 0.1.0 (2be4034 2022-03-31). 16 | fn build_info() -> String { 17 | let spin_version = env_var("SPIN_VERSION"); 18 | let spin_commit_sha = env_var("SPIN_COMMIT_SHA"); 19 | let spin_commit_date = env_var("SPIN_COMMIT_DATE"); 20 | format!("{spin_version} ({spin_commit_sha} {spin_commit_date})") 21 | } 22 | 23 | fn env_var(name: &str) -> String { 24 | std::env::var(name).unwrap_or_else(|_| "unknown".to_string()) 25 | } 26 | -------------------------------------------------------------------------------- /spin-mqtt.wit: -------------------------------------------------------------------------------- 1 | // WIT design guidelines https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md 2 | // https://component-model.bytecodealliance.org/design/wit.html 3 | package spin:mqtt-trigger; 4 | 5 | interface spin-mqtt-types { 6 | /// General purpose error. 7 | variant error { 8 | other(string), 9 | } 10 | 11 | /// MQTT QoS levels. 12 | enum qos { 13 | at-most-once, 14 | at-least-once, 15 | exactly-once, 16 | } 17 | 18 | // metadata associated with the payload 19 | record metadata { 20 | topic: string, 21 | } 22 | 23 | /// The message payload. 24 | type payload = list; 25 | } 26 | 27 | world spin-mqtt { 28 | use spin-mqtt-types.{error, metadata, payload}; 29 | 30 | /// The entrypoint for a Mqtt handler in wasm component 31 | export handle-message: func(message: payload, metadata: metadata) -> result<_, error>; 32 | } 33 | 34 | world spin-mqtt-sdk { 35 | import spin-mqtt-types; 36 | } -------------------------------------------------------------------------------- /templates/mqtt-rust/metadata/spin-template.toml: -------------------------------------------------------------------------------- 1 | manifest_version = "1" 2 | id = "mqtt-rust" 3 | description = "Mqtt message handler/trigger using Rust." 4 | tags = ["mqtt", "rust"] 5 | 6 | [parameters] 7 | project-description = { type = "string", prompt = "Description", default = "" } 8 | mqtt-address = { type = "string", prompt = "Mqtt Address", default = "mqtt://localhost:1883" } 9 | mqtt-username = { type = "string", prompt = "Mqtt Username", default = "" } 10 | mqtt-password = { type = "string", prompt = "Mqtt Password", default = "" } 11 | # Mqtt Keep Alive Interval is pattern matched with a number between 0-65535. 12 | mqtt-keep_alive_interval = { type = "string", prompt = "Mqtt Keep Alive Interval (Secs between 0-65535)", default = "30", pattern = "^(6553[0-5]|655[0-2]\\d|65[0-4]\\d{2}|6[0-4]\\d{3}|[1-5]\\d{4}|[1-9]\\d{0,3}|0)$"} 13 | mqtt-topic = { type = "string", prompt = "Mqtt Topic" } 14 | # Mqtt QoS for Topic is pattern matched with a number between 0-2. 15 | mqtt-qos = { type = "string", prompt = "Mqtt QoS for Topic (between 0-2)", default = "1", pattern = "^[0-2]$" } 16 | -------------------------------------------------------------------------------- /examples/mqtt-app/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "mqtt-app" 5 | version = "0.1.0" 6 | description = "Demo app to receive MQTT messages." 7 | authors = ["The Spin authors"] 8 | 9 | [application.trigger.mqtt] 10 | address = "mqtt://localhost:1883" 11 | username = "admin" 12 | password = "password" 13 | keep_alive_interval = "30" 14 | 15 | [[trigger.mqtt]] 16 | id = "trigger-mqtt-c01" 17 | component = "mqtt-c01" 18 | topic = "messages-in01" 19 | qos = "1" 20 | 21 | [[trigger.mqtt]] 22 | id = "trigger-mqtt-c02" 23 | component = "mqtt-c02" 24 | topic = "messages-in02" 25 | qos = "0" 26 | 27 | [component.mqtt-c01] 28 | source = "target/wasm32-wasip2/release/mqtt_app.wasm" 29 | allowed_outbound_hosts = ["mqtt://localhost:1883"] 30 | 31 | [component.mqtt-c01.build] 32 | command = "cargo build --target wasm32-wasip2 --release" 33 | 34 | [component.mqtt-c02] 35 | source = "target/wasm32-wasip2/release/mqtt_app.wasm" 36 | allowed_outbound_hosts = ["mqtt://localhost:1883"] 37 | 38 | [component.mqtt-c02.build] 39 | command = "cargo build --target wasm32-wasip2 --release" 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Suneet Nangia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile to build and run MQTT plugin for Spin framework. 2 | 3 | .PHONY: all 4 | all: build_plugin install_plugin 5 | 6 | .PHONY: lint 7 | lint: 8 | @echo "Running linting check..." 9 | cargo clippy --all --all-targets -- -D warnings 10 | cargo fmt --all -- --check 11 | 12 | .PHONY: lint-rust-examples 13 | lint-rust-examples: 14 | @echo "Running linting check on example..." \ 15 | && cargo clippy --manifest-path "examples/mqtt-app/Cargo.toml" -- -D warnings \ 16 | && cargo fmt --manifest-path "examples/mqtt-app/Cargo.toml" -- --check \ 17 | 18 | .PHONY: lint-all 19 | lint-all: lint lint-rust-examples 20 | 21 | .PHONY: build_plugin 22 | build_plugin: 23 | @echo "Building Mqtt Plugin..." 24 | cargo build --release 25 | 26 | .PHONY: install_plugin 27 | install_plugin: 28 | @echo "Installing Mqtt Plugin in Spin..." 29 | spin plugins update && spin plugins upgrade pluginify -y 30 | spin pluginify --install 31 | 32 | .PHONY: clean 33 | clean: 34 | @echo "Cleaning up..." 35 | cargo clean 36 | cargo clean --manifest-path ./examples/mqtt-app/Cargo.toml 37 | rm -f trigger-mqtt-*.tar.gz 38 | rm -f trigger-mqtt.json 39 | spin plugin uninstall trigger-mqtt 40 | 41 | .PHONY: test 42 | test: 43 | @echo "Running integration test..." 44 | bash tests/integration_test.sh -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu 3 | { 4 | "name": "MQTT Spin Trigger SDK/Plugin", 5 | "image": "mcr.microsoft.com/devcontainers/rust:1-bookworm", 6 | 7 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 8 | // "forwardPorts": [], 9 | 10 | // Use 'postCreateCommand' to run commands after the container is created. 11 | "postCreateCommand": "sh ./.devcontainer/bootstrap.sh", 12 | 13 | // Configure tool-specific properties. 14 | // "customizations": {}, 15 | 16 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 17 | "remoteUser": "root", 18 | 19 | // Add the IDs of extensions you want installed when the container is created. 20 | "customizations": { 21 | "vscode": { 22 | "extensions": [ 23 | "rust-lang.rust-analyzer", 24 | "tamasfe.even-better-toml", 25 | "vadimcn.vscode-lldb", 26 | "usernamehw.errorlens", 27 | "gruntfuggly.todo-tree", 28 | "github.copilot-chat", 29 | "streetsidesoftware.code-spell-checker", 30 | "ms-vscode.makefile-tools", 31 | "davidanson.vscode-markdownlint", 32 | "bytecodealliance.wit-idl", 33 | "ms-kubernetes-tools.vscode-kubernetes-tools" 34 | ] 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trigger-mqtt" 3 | version = "0.6.0" 4 | edition = { workspace = true } 5 | rust-version = { workspace = true } 6 | 7 | [dependencies] 8 | anyhow = "1.0.100" 9 | clap = { version = "3.2.25", features = ["derive", "env"] } 10 | futures = "0.3.31" 11 | serde = "1.0.228" 12 | spin-app = { git = "https://github.com/spinframework/spin", tag = "v3.5.0" } 13 | spin-core = { git = "https://github.com/spinframework/spin", tag = "v3.5.0" } 14 | spin-expressions = { git = "https://github.com/spinframework/spin", tag = "v3.5.0" } 15 | spin-factors = { git = "https://github.com/spinframework/spin", tag = "v3.5.0" } 16 | spin-runtime-factors = { git = "https://github.com/spinframework/spin", tag = "v3.5.0" } 17 | spin-trigger = { git = "https://github.com/spinframework/spin", tag = "v3.5.0" } 18 | spin-telemetry = { git = "https://github.com/spinframework/spin", tag = "v3.5.0" } 19 | spin-factor-variables = { git = "https://github.com/spinframework/spin", tag = "v3.5.0" } 20 | tokio = { version = "1", features = ["full"] } 21 | tracing = { version = "0.1.41", features = ["log"] } 22 | paho-mqtt = { version = "0.13.3", features = ["vendored-ssl"] } 23 | wasmtime = { version = "37.0.2" } 24 | 25 | [workspace] 26 | members = ["sdk", "sdk/macro"] 27 | 28 | [workspace.package] 29 | version = "0.2.0" 30 | edition = "2021" 31 | rust-version = "1.87" 32 | 33 | [workspace.dependencies] 34 | wit-bindgen = "0.43.0" 35 | -------------------------------------------------------------------------------- /release-process.md: -------------------------------------------------------------------------------- 1 | # Cutting a new release of the Spin MQTT trigger plugin 2 | 3 | To cut a new release of the MQTT trigger plugin, you will need to do the following: 4 | 5 | 1. Confirm that [CI is green](https://github.com/spinframework/spin-trigger-mqtt/actions) for the commit selected to be tagged and released. 6 | 7 | 2. Change the version number in [Cargo.toml](./Cargo.toml) and [spin-pluginify.toml](./spin-pluginify.toml) and run `cargo build --release`. 8 | 9 | 3. Create a pull request with these changes and merge once approved. 10 | 11 | 4. Checkout the commit with the version bump from above. 12 | 13 | 5. Create and push a new tag with a `v` and then the version number. 14 | 15 | As an example, via the `git` CLI: 16 | 17 | ``` 18 | # Create a GPG-signed and annotated tag 19 | git tag -s -m "Spin MQTT Trigger v0.2.0" v0.2.0 20 | 21 | # Push the tag to the remote corresponding to spinframework/spin-trigger-mqtt (here 'origin') 22 | git push origin v0.2.0 23 | ``` 24 | 25 | 6. Pushing the tag upstream will trigger the [release action](https://github.com/spinframework/spin-trigger-mqtt/actions/workflows/release.yml). 26 | - The release build will create the packaged versions of the plugin, the updated plugin manifest and a checksums file 27 | - These assets are uploaded to a new GitHub release for the pushed tag 28 | - Release notes are auto-generated but edit as needed especially around breaking changes or other notable items 29 | 30 | 7. Create a PR in the [spinframework/spin-plugins](https://github.com/spinframework/spin-plugins) repository with the [updated manifest](https://github.com/spinframework/spin-plugins/tree/main/manifests/mqtt-trigger). 31 | 32 | 8. If applicable, create PR(s) or coordinate [documentation](https://github.com/spinframework/spin-docs) needs, e.g. for new features or updated functionality. -------------------------------------------------------------------------------- /sdk/macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | 4 | const WIT_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../spin-mqtt.wit"); 5 | 6 | #[proc_macro_attribute] 7 | pub fn mqtt_component(_attr: TokenStream, item: TokenStream) -> TokenStream { 8 | let func = syn::parse_macro_input!(item as syn::ItemFn); 9 | let func_name = &func.sig.ident; 10 | let await_postfix = func.sig.asyncness.map(|_| quote!(.await)); 11 | let preamble = preamble(); 12 | 13 | quote!( 14 | #func 15 | mod __spin_mqtt { 16 | mod preamble { 17 | #preamble 18 | } 19 | impl self::preamble::Guest for preamble::Mqtt { 20 | fn handle_message(payload: ::spin_mqtt_sdk::Payload, metadata: ::spin_mqtt_sdk::Metadata) -> ::std::result::Result<(), ::spin_mqtt_sdk::Error> { 21 | ::spin_mqtt_sdk::executor::run(async move { 22 | match super::#func_name(payload, metadata)#await_postfix { 23 | ::std::result::Result::Ok(()) => ::std::result::Result::Ok(()), 24 | ::std::result::Result::Err(e) => { 25 | eprintln!("{}", e); 26 | ::std::result::Result::Err(::spin_mqtt_sdk::Error::Other(e.to_string())) 27 | }, 28 | } 29 | }) 30 | } 31 | } 32 | } 33 | ).into() 34 | } 35 | 36 | fn preamble() -> proc_macro2::TokenStream { 37 | let world = "spin-mqtt"; 38 | quote! { 39 | #![allow(missing_docs)] 40 | ::spin_mqtt_sdk::wit_bindgen::generate!({ 41 | world: #world, 42 | path: #WIT_PATH, 43 | runtime_path: "::spin_mqtt_sdk::wit_bindgen::rt", 44 | with: { 45 | "spin:mqtt-trigger/spin-mqtt-types": ::spin_mqtt_sdk, 46 | } 47 | }); 48 | pub struct Mqtt; 49 | export!(Mqtt); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # An MQTT Trigger/Plugin for Fermyon Spin Framework 2 | 3 | MQTT is a dominant communication protocol in IoT and edge scenarios, used by major products and services in manufacturing, automotive and other industries. 4 | Objective of this repo is to provide a robust plugin/trigger to receive MQTT messages in the Spin based wasm components. 5 | 6 | ## Usage Guidance 7 | 8 | This plugin is a trigger plugin i.e. it is activated when message is received on a configured MQTT topic. 9 | The plugin then instantiates a Wasm component and injects the message to the component, which in turn process the message and can optionally publish the messages to any of the available targets in Spin e.g. MQTT, Redis, Http endpoints. 10 | 11 | ### Install Plugin 12 | 13 | Install MQTT Plugin: 14 | 15 | ```bash 16 | spin plugin install --url https://github.com/spinframework/spin-trigger-mqtt/releases/download/canary/trigger-mqtt.json --yes 17 | ``` 18 | 19 | [Note: release management for multiple versions of this plugin/trigger will be added soon] 20 | 21 | If you want to learn more about Spin's plugin model, read [here](https://www.fermyon.com/blog/managing-spin-templates-and-plugins). 22 | 23 | ### Install Template 24 | 25 | [Spin templates](https://www.fermyon.com/blog/managing-spin-templates-and-plugins) allow a Spin developer to quickly create the skeleton of an application or component, ready for the application logic to be filled in. As part of this repo, a new template is created to help build applications which make use of MQTT as a communication protocol/trigger. 26 | 27 | Install MQTT Template: 28 | 29 | ```bash 30 | spin templates install --git https://github.com/spinframework/spin-trigger-mqtt --upgrade 31 | ``` 32 | 33 | ### Create Spin App 34 | 35 | ```bash 36 | spin new -t mqtt-rust mqtt-app 37 | ``` 38 | 39 | ## Templating `mqtt` Configuration in `spin.toml` 40 | 41 | The `address`, `username`, `password` and `topic` support the ability to be configured using Spin variables. An example of configuring the password using env variables: 42 | 43 | ```toml 44 | #spin.toml 45 | spin_manifest_version = 2 46 | 47 | [application] 48 | name = "mqtt-app" 49 | version = "0.1.0" 50 | description = "Demo app to receive MQTT messages." 51 | authors = ["Suneet Nangia "] 52 | 53 | [variables] 54 | password = { required = true } 55 | 56 | [application.trigger.mqtt] 57 | address = "mqtt://localhost:1883" 58 | username = "user" 59 | password = "{{ password }}" 60 | keep_alive_interval = "30" 61 | ... 62 | ``` 63 | 64 | To inject the Spin variable using environment variables: 65 | 66 | ```bash 67 | SPIN_VARIABLE_PASSWORD=password spin up 68 | ``` 69 | 70 | To skip authentication, set the `username` and `password` fields to empty strings: 71 | 72 | ```toml 73 | [application.trigger.mqtt] 74 | address = "mqtt://localhost:1883" 75 | username = "admin" 76 | password = "public" 77 | keep_alive_interval = "30" 78 | ``` 79 | 80 | ## State of Play 81 | 82 | 1. Authenticates using anonymous and username/password to MQTT server. 83 | 2. Receive messages from an MQTT topic per configured QoS. 84 | 85 | [more MQTT client/subscription attributes will be available soon] 86 | 87 | ## Running an MQTT Broker 88 | 89 | Download [MQTTX CLI](https://github.com/emqx/MQTTX/tree/main/cli) 90 | 91 | ```sh 92 | brew install emqx/mqttx/mqttx-cli 93 | ``` 94 | 95 | Run the EMQX broker: https://mqttx.app/docs/get-started 96 | 97 | ```sh 98 | docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx 99 | ``` 100 | 101 | The default username and password of the broker is `admin` and `public`. 102 | 103 | > Alternatively, use [Mosquitto's public MQTT broker](https://test.mosquitto.org/) without authentication by setting the broker hostname to `test.mosquitto.org`. 104 | 105 | ## Dev Loop [Build and Install from Source] 106 | 107 | For this simple dev loop, make sure you have access to an MQTT broker. The following steps assume you followed the section to [run an MQTT broker locally](#running-an-mqtt-broker). 108 | 109 | * Open the repo in Dev Container or in pre-configured GitHub [Codespace](https://codespaces.new/spinframework/spin-trigger-mqtt) 110 | * Run ```make``` to build and install the plugin locally. 111 | * Update ```examples/mqtt-app/spin.toml``` to reflect your MQTT server details and ensure it's accessible on the network. 112 | * Run ```spin build --up --from examples/mqtt-app/spin.toml``` to run the example Spin app. 113 | * Run ```mqttx pub -t 'messages-in01' -h 'localhost' -p 1883 -u 'admin' -P 'public' -m 'Hello to MQTT Spin Component!'``` with the hostname and credentials for your server, to publish the message which is then received by Spin app. 114 | * Optionally, run ```make clean``` to clean up and rebuild and install the plugin locally. 115 | -------------------------------------------------------------------------------- /tests/integration_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 6 | PROJECT_DIR="$(dirname "$SCRIPT_DIR")" 7 | MQTT_CONTAINER_NAME="emqx-test" 8 | MQTT_HOST="localhost" 9 | MQTT_PORT="1883" 10 | MQTT_USERNAME="admin" 11 | MQTT_PASSWORD="public" 12 | TEST_TOPIC="messages-in01" 13 | TEST_MESSAGE="Hello to MQTT Spin Component!" 14 | 15 | # Colors for output 16 | RED='\033[0;31m' 17 | GREEN='\033[0;32m' 18 | YELLOW='\033[1;33m' 19 | NC='\033[0m' # No Color 20 | 21 | log() { 22 | echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" 23 | } 24 | 25 | warn() { 26 | echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}" 27 | } 28 | 29 | error() { 30 | echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}" 31 | } 32 | 33 | cleanup() { 34 | log "Cleaning up test environment..." 35 | 36 | # Kill spin process if running 37 | if [ ! -z "${SPIN_PID:-}" ]; then 38 | if kill -0 "$SPIN_PID" 2>/dev/null; then 39 | log "Stopping Spin application (PID: $SPIN_PID)..." 40 | kill "$SPIN_PID" 41 | wait "$SPIN_PID" 2>/dev/null || true 42 | log "Spin application stopped" 43 | fi 44 | fi 45 | 46 | # Stop and remove MQTT broker container 47 | docker stop "$MQTT_CONTAINER_NAME" 2>/dev/null || true 48 | docker rm "$MQTT_CONTAINER_NAME" 2>/dev/null || true 49 | 50 | log "Cleanup completed" 51 | } 52 | 53 | # Set up cleanup trap 54 | trap cleanup EXIT 55 | 56 | check_dependencies() { 57 | log "Checking dependencies..." 58 | 59 | # Check if docker is available 60 | if ! command -v docker &> /dev/null; then 61 | error "Docker is required but not installed" 62 | exit 1 63 | fi 64 | 65 | # Check if mqttx is available 66 | if ! command -v mqttx &> /dev/null; then 67 | error "mqttx CLI is required but not installed. Run 'brew install emqx/mqttx/mqttx-cli' or see installation instructions: https://mqttx.app/docs/get-started" 68 | exit 1 69 | fi 70 | 71 | # Check if spin is available 72 | if ! command -v spin &> /dev/null; then 73 | error "Spin CLI is required but not installed" 74 | exit 1 75 | fi 76 | 77 | log "Dependencies check completed" 78 | } 79 | 80 | start_mqtt_broker() { 81 | log "Starting MQTT broker..." 82 | 83 | # Stop existing container if running 84 | docker stop "$MQTT_CONTAINER_NAME" 2>/dev/null || true 85 | docker rm "$MQTT_CONTAINER_NAME" 2>/dev/null || true 86 | 87 | # Start EMQX broker 88 | docker run -d \ 89 | --name "$MQTT_CONTAINER_NAME" \ 90 | -p 1883:1883 \ 91 | -p 8083:8083 \ 92 | -p 8883:8883 \ 93 | -p 8084:8084 \ 94 | -p 18083:18083 \ 95 | emqx/emqx 96 | 97 | log "Waiting for MQTT broker to be ready..." 98 | 99 | # Wait for broker to be ready (max 30 seconds) 100 | for i in {1..30}; do 101 | if mqttx pub -t "testing" -h "$MQTT_HOST" -p "$MQTT_PORT" -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" -m "test message" &>/dev/null; then 102 | log "MQTT broker is ready" 103 | return 0 104 | fi 105 | sleep 1 106 | done 107 | 108 | error "MQTT broker failed to start or is not accessible" 109 | docker logs "$MQTT_CONTAINER_NAME" 110 | exit 1 111 | } 112 | 113 | build_and_install_plugin() { 114 | log "Building and installing MQTT plugin..." 115 | 116 | cd "$PROJECT_DIR" 117 | 118 | # Run make to build and install plugin 119 | make clean || true 120 | make 121 | 122 | log "Plugin built and installed successfully" 123 | } 124 | 125 | start_spin_app() { 126 | log "Starting Spin application..." 127 | 128 | cd "$PROJECT_DIR" 129 | 130 | # Create log file for spin output (overwrite if exists) 131 | SPIN_LOG_DIR="$PROJECT_DIR/logs" 132 | SPIN_LOGS_STDOUT="$SPIN_LOG_DIR/mqtt-c01_stdout.txt" 133 | 134 | # Build and start the example app in background, capturing output 135 | spin build --up --from examples/mqtt-app/spin.toml --log-dir "$SPIN_LOG_DIR" & 136 | SPIN_PID=$! 137 | 138 | log "Waiting for Spin application to start..." 139 | 140 | # Wait for spin app to be ready (max 30 seconds) 141 | for i in {1..30}; do 142 | if kill -0 "$SPIN_PID" 2>/dev/null; then 143 | sleep 2 # Give it a bit more time to fully initialize 144 | log "Spin application is running (PID: $SPIN_PID)" 145 | log "Spin logs being written to: $SPIN_LOG_DIR" 146 | return 0 147 | fi 148 | sleep 1 149 | done 150 | 151 | error "Spin application failed to start" 152 | exit 1 153 | } 154 | 155 | test_mqtt_message_flow() { 156 | log "Testing MQTT message flow..." 157 | 158 | # Give the system a moment to stabilize 159 | sleep 3 160 | 161 | log "Publishing test message to topic '$TEST_TOPIC'..." 162 | 163 | # Publish message to MQTT broker 164 | mqttx pub \ 165 | -t "$TEST_TOPIC" \ 166 | -h "$MQTT_HOST" \ 167 | -p "$MQTT_PORT" \ 168 | -u "$MQTT_USERNAME" \ 169 | -P "$MQTT_PASSWORD" \ 170 | -m "$TEST_MESSAGE" 171 | 172 | log "Message published successfully" 173 | 174 | # Wait a bit for message processing 175 | sleep 5 176 | 177 | # Check if spin process is still running (it should be) 178 | if ! kill -0 "$SPIN_PID" 2>/dev/null; then 179 | error "Spin application stopped unexpectedly" 180 | exit 1 181 | fi 182 | 183 | # Check if the test message appears in the spin logs 184 | log "Checking if Spin application received the message..." 185 | if grep -q "$TEST_MESSAGE" "$SPIN_LOGS_STDOUT"; then 186 | log "✅ SUCCESS: Test message found in Spin application output!" 187 | else 188 | error "❌ FAILURE: Test message '$TEST_MESSAGE' not found in Spin output" 189 | log "Full Spin output:" 190 | cat "$SPIN_LOGS_STDOUT" 191 | exit 1 192 | fi 193 | 194 | rm -rf "$SPIN_LOG_DIR" || true 195 | 196 | log "MQTT message flow test completed successfully" 197 | } 198 | 199 | run_integration_test() { 200 | log "Starting MQTT Trigger Plugin Integration Test" 201 | log "==============================================" 202 | 203 | check_dependencies 204 | start_mqtt_broker 205 | build_and_install_plugin 206 | start_spin_app 207 | test_mqtt_message_flow 208 | 209 | log "==============================================" 210 | log "Integration test completed successfully!" 211 | } 212 | 213 | # Run the test if script is executed directly 214 | if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then 215 | run_integration_test 216 | fi -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 2 | 3 | name: Build and package 4 | on: 5 | push: 6 | branches: [main] 7 | tags: ["v*"] 8 | pull_request: 9 | branches: [main] 10 | workflow_dispatch: 11 | 12 | # TODO: better way? 13 | permissions: 14 | contents: write 15 | 16 | # Construct a concurrency group to be shared across workflow runs. 17 | # The default behavior ensures that only one is running at a time, with 18 | # all others queuing and thus not interrupting runs that are in-flight. 19 | concurrency: ${{ github.workflow }} 20 | 21 | env: 22 | PROGRAM_NAME: trigger-mqtt 23 | 24 | jobs: 25 | lint: 26 | name: Lint Rust code 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v4 31 | - name: Install Rust 32 | uses: dtolnay/rust-toolchain@stable 33 | with: 34 | toolchain: 1.88 35 | 36 | - name: Install dependencies 37 | run: | 38 | rustup component add rustfmt 39 | rustup component add clippy 40 | 41 | - name: lint code 42 | run: make lint 43 | 44 | - name: audit dependencies 45 | run: | 46 | cargo install cargo-audit --locked 47 | cargo audit 48 | 49 | - name: Run Clippy 50 | run: cargo clippy --all -- -D warnings 51 | - name: Cancel everything if linting fails 52 | if: failure() 53 | uses: andymckay/cancel-action@0.2 54 | 55 | build: 56 | name: Build plugin 57 | runs-on: ${{ matrix.config.os }} 58 | strategy: 59 | fail-fast: false 60 | matrix: 61 | config: 62 | - { target: "x86_64-unknown-linux-gnu", os: "ubuntu-22.04", arch: "amd64", extension: "" } 63 | - { target: "aarch64-unknown-linux-gnu", os: "ubuntu-22.04", arch: "aarch64", extension: "" } 64 | - { target: "x86_64-apple-darwin", os: "macos-15-intel", arch: "amd64", extension: "" } 65 | - { target: "aarch64-apple-darwin", os: "macos-14", arch: "aarch64", extension: "" } 66 | - { target: "x86_64-pc-windows-msvc", os: "windows-latest", arch: "amd64", extension: ".exe" } 67 | steps: 68 | - uses: actions/checkout@v4 69 | - uses: Swatinem/rust-cache@v2 70 | with: 71 | shared-key: "${{ runner.os }}-lint-${{ hashFiles('./Cargo.lock') }}" 72 | cache-on-failure: "true" 73 | - name: Install Rust 74 | uses: dtolnay/rust-toolchain@stable 75 | with: 76 | toolchain: 1.88 77 | targets: ${{ matrix.config.target }} 78 | - name: Install Spin 79 | uses: rajatjindal/setup-actions/spin@main 80 | with: 81 | version: "v3.3.0" 82 | - name: Install pluginify 83 | shell: bash 84 | run: spin plugins install --url https://github.com/itowlson/spin-pluginify/releases/download/canary/pluginify.json --yes 85 | - name: Set up for cross-compiled linux aarch64 build 86 | if: matrix.config.target == 'aarch64-unknown-linux-gnu' 87 | run: | 88 | sudo apt update 89 | sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu 90 | echo '[target.aarch64-unknown-linux-gnu]' >> ${HOME}/.cargo/config.toml 91 | echo 'linker = "aarch64-linux-gnu-gcc"' >> ${HOME}/.cargo/config.toml 92 | - name: Build plugin binary 93 | run: cargo build --release --target ${{ matrix.config.target }} 94 | - name: Copy plugin binary to standard location 95 | shell: bash 96 | run: cp target/${{ matrix.config.target }}/release/${{ env.PROGRAM_NAME}}${{ matrix.config.extension }} target/release/${{ env.PROGRAM_NAME}}${{ matrix.config.extension }} 97 | 98 | - name: Pluginify plugin binary 99 | run: spin pluginify --arch ${{ matrix.config.arch }} 100 | - name: Archive pluginified 101 | uses: actions/upload-artifact@v4 102 | with: 103 | name: ${{ env.PROGRAM_NAME}}-${{ matrix.config.os }}-${{ matrix.config.arch }} 104 | path: | 105 | *.tar.gz 106 | *.json 107 | 108 | package: 109 | name: Package plugin 110 | if: github.event_name == 'push' 111 | runs-on: ubuntu-latest 112 | needs: build 113 | steps: 114 | - name: Install Spin 115 | uses: rajatjindal/setup-actions/spin@main 116 | with: 117 | version: "v2.4.2" 118 | - name: Install pluginify 119 | shell: bash 120 | run: spin plugins install --url https://github.com/itowlson/spin-pluginify/releases/download/canary/pluginify.json --yes 121 | 122 | - name: set the release version (tag) 123 | if: startsWith(github.ref, 'refs/tags/v') 124 | shell: bash 125 | run: echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV 126 | - name: set the release version (main) 127 | if: github.ref == 'refs/heads/main' 128 | shell: bash 129 | run: echo "RELEASE_VERSION=canary" >> $GITHUB_ENV 130 | - name: set the release version (TEST TEST TEST) 131 | if: github.event_name == 'pull_request' 132 | shell: bash 133 | run: echo "RELEASE_VERSION=precanary" >> $GITHUB_ENV 134 | 135 | - name: Download artifacts 136 | uses: actions/download-artifact@v4 137 | - name: Display structure of downloaded files 138 | run: ls -R 139 | - name: pluginify it 140 | run: | 141 | spin pluginify --merge --release-url-base https://github.com/${{ github.repository }}/releases/download/${{ env.RELEASE_VERSION }}/ >${{ env.PROGRAM_NAME }}.json 142 | - name: Display merged manifest 143 | run: cat ${{ env.PROGRAM_NAME }}.json 144 | 145 | # Handle versioned release 146 | - name: Upload tars to Github release 147 | if: startsWith(github.ref, 'refs/tags/v') 148 | uses: svenstaro/upload-release-action@v2 149 | with: 150 | repo_token: ${{ secrets.GITHUB_TOKEN }} 151 | file: "**/*.tar.gz" 152 | file_glob: true 153 | tag: ${{ github.ref }} 154 | - name: Upload manifest to Github release 155 | if: startsWith(github.ref, 'refs/tags/v') 156 | uses: svenstaro/upload-release-action@v2 157 | with: 158 | repo_token: ${{ secrets.GITHUB_TOKEN }} 159 | file: ${{ env.PROGRAM_NAME }}.json 160 | tag: ${{ github.ref }} 161 | 162 | # Handle canary release 163 | - name: Delete canary tag 164 | if: github.ref == 'refs/heads/main' 165 | uses: dev-drprasad/delete-tag-and-release@v0.2.1 166 | env: 167 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 168 | with: 169 | tag_name: canary 170 | - name: Recreate canary tag and release 171 | if: github.ref == 'refs/heads/main' 172 | uses: ncipollo/release-action@v1.10.0 173 | with: 174 | tag: canary 175 | allowUpdates: true 176 | prerelease: true 177 | - name: Upload tars to Github release 178 | if: github.ref == 'refs/heads/main' 179 | uses: svenstaro/upload-release-action@v2 180 | with: 181 | repo_token: ${{ secrets.GITHUB_TOKEN }} 182 | file: "**/*.tar.gz" 183 | file_glob: true 184 | tag: canary 185 | - name: Upload manifest to Github release 186 | if: github.ref == 'refs/heads/main' 187 | uses: svenstaro/upload-release-action@v2 188 | with: 189 | repo_token: ${{ secrets.GITHUB_TOKEN }} 190 | file: ${{ env.PROGRAM_NAME }}.json 191 | tag: canary 192 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Context}; 2 | use clap::Args; 3 | use paho_mqtt::AsyncClient; 4 | use serde::{Deserialize, Serialize}; 5 | use spin_app::App; 6 | use spin_factor_variables::VariablesFactor; 7 | use spin_factors::RuntimeFactors; 8 | use spin_trigger::{Trigger, TriggerApp}; 9 | use std::{sync::Arc, time::Duration}; 10 | 11 | // https://docs.rs/wasmtime/latest/wasmtime/component/macro.bindgen.html 12 | wasmtime::component::bindgen!({ 13 | path: ".", 14 | world: "spin-mqtt", 15 | imports: { default: async }, 16 | exports: { default: async }, 17 | }); 18 | 19 | use spin::mqtt_trigger::spin_mqtt_types as mqtt_types; 20 | 21 | // The trigger structure with all values processed and ready 22 | #[derive(Clone)] 23 | pub struct MqttTrigger { 24 | /// Trigger settings 25 | metadata: TriggerMetadata, 26 | /// Per-component settings 27 | component_configs: Vec, 28 | /// Whether to run in test mode 29 | test: bool, 30 | } 31 | 32 | impl Trigger for MqttTrigger { 33 | const TYPE: &'static str = "mqtt"; 34 | type InstanceState = (); 35 | type CliArgs = CliArgs; 36 | 37 | fn new(cli_args: Self::CliArgs, app: &App) -> anyhow::Result { 38 | let trigger_type = >::TYPE; 39 | let metadata = app 40 | .get_trigger_metadata::(trigger_type)? 41 | .unwrap_or_default(); 42 | 43 | let component_configs = app 44 | .trigger_configs::(trigger_type)? 45 | .into_iter() 46 | .map(|(_, config)| config) 47 | .collect(); 48 | 49 | Ok(Self { 50 | metadata, 51 | component_configs, 52 | test: cli_args.test, 53 | }) 54 | } 55 | 56 | async fn run(mut self, trigger_app: TriggerApp) -> anyhow::Result<()> { 57 | if self.test { 58 | for component in &self.component_configs { 59 | self.handle_mqtt_event( 60 | &trigger_app, 61 | &component.component, 62 | b"test message".to_vec(), 63 | "test".to_string(), 64 | ) 65 | .await?; 66 | } 67 | 68 | Ok(()) 69 | } else { 70 | self.metadata.resolve_variables(&trigger_app).await?; 71 | tokio::spawn(async move { 72 | // This trigger spawns threads, which Ctrl+C does not kill. So 73 | // for this case we need to detect Ctrl+C and shut those threads 74 | // down. For simplicity, we do this by terminating the process. 75 | tokio::signal::ctrl_c() 76 | .await 77 | .expect("failed to listen for Ctrl+C"); 78 | std::process::exit(0); 79 | }); 80 | 81 | let trigger = Arc::new(self); 82 | let trigger_app = Arc::new(trigger_app); 83 | let tasks = trigger.component_configs.iter().map(|component_config| { 84 | let trigger = trigger.clone(); 85 | let trigger_app = trigger_app.clone(); 86 | let component_config = component_config.clone(); 87 | tokio::spawn( 88 | async move { trigger.run_listener(&trigger_app, component_config).await }, 89 | ) 90 | }); 91 | 92 | // wait for the first handle to be returned and drop the rest 93 | let (result, _, rest) = futures::future::select_all(tasks).await; 94 | 95 | drop(rest); 96 | result? 97 | } 98 | } 99 | } 100 | 101 | impl MqttTrigger { 102 | /// Handle a specific MQTT event 103 | async fn handle_mqtt_event( 104 | &self, 105 | trigger_app: &TriggerApp, 106 | component_id: &str, 107 | message: Vec, 108 | topic: String, 109 | ) -> anyhow::Result<()> { 110 | // Load the guest wasm component 111 | let instance_builder = trigger_app.prepare(component_id)?; 112 | let (instance, mut store) = instance_builder.instantiate(()).await?; 113 | // SpinMqtt is auto generated by bindgen as per WIT files referenced above. 114 | let instance = SpinMqtt::new(&mut store, &instance)?; 115 | 116 | instance 117 | .call_handle_message(store, &message, &mqtt_types::Metadata { topic }) 118 | .await? 119 | .map_err(|err| anyhow!("failed to execute guest: {err}")) 120 | } 121 | 122 | /// Run the listener for a specific component 123 | async fn run_listener( 124 | &self, 125 | trigger_app: &TriggerApp, 126 | config: ComponentConfig, 127 | ) -> anyhow::Result<()> { 128 | let topic = resolve_variables(trigger_app, config.topic).await?; 129 | 130 | // Receive the messages here from the specific topic in mqtt broker. 131 | let mut client = AsyncClient::new(self.metadata.address.as_str())?; 132 | let keep_alive_interval = self.metadata.keep_alive_interval.parse::()?; 133 | let conn_opts = paho_mqtt::ConnectOptionsBuilder::new() 134 | .keep_alive_interval(Duration::from_secs(keep_alive_interval)) 135 | .user_name(&self.metadata.username) 136 | .password(&self.metadata.password) 137 | .finalize(); 138 | 139 | client 140 | .connect(conn_opts) 141 | .await 142 | .context(format!("failed to connect to '{}'", self.metadata.address))?; 143 | let qos = config.qos.parse::()?; 144 | client 145 | .subscribe(&topic, qos) 146 | .await 147 | .context(format!("failed to subscribe to '{topic}'"))?; 148 | 149 | // TODO: Should the buffer be bounded/configurable? 150 | let rx = client.get_stream(None); 151 | 152 | loop { 153 | match rx.recv().await { 154 | Ok(Some(msg)) => { 155 | // Handle the received message 156 | if let Err(e) = self 157 | .handle_mqtt_event( 158 | trigger_app, 159 | &config.component, 160 | msg.payload().to_vec(), 161 | msg.topic().to_owned(), 162 | ) 163 | .await 164 | { 165 | tracing::error!("Error handling MQTT message: {:?}", e); 166 | } 167 | } 168 | Ok(None) => { 169 | // Todo: Figure out what this case is 170 | } 171 | Err(_) => { 172 | // Channel is empty and closed 173 | break; 174 | } 175 | } 176 | } 177 | 178 | Ok(()) 179 | } 180 | } 181 | 182 | /// Command line arguments 183 | #[derive(Args)] 184 | pub struct CliArgs { 185 | /// If true, run each component once and exit 186 | #[clap(long)] 187 | pub test: bool, 188 | } 189 | 190 | // Trigger settings (raw serialization format) 191 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 192 | #[serde(deny_unknown_fields)] 193 | struct TriggerMetadata { 194 | address: String, 195 | username: String, 196 | password: String, 197 | keep_alive_interval: String, 198 | } 199 | 200 | impl TriggerMetadata { 201 | /// Resolve any variables inside the trigger metadata. 202 | async fn resolve_variables( 203 | &mut self, 204 | trigger_app: &TriggerApp, 205 | ) -> anyhow::Result<()> { 206 | let address = resolve_variables(trigger_app, self.address.clone()).await?; 207 | let username = resolve_variables(trigger_app, self.username.clone()).await?; 208 | let password = resolve_variables(trigger_app, self.password.clone()).await?; 209 | self.address = address; 210 | self.username = username; 211 | self.password = password; 212 | Ok(()) 213 | } 214 | } 215 | 216 | // Per-component settings (raw serialization format) 217 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 218 | #[serde(deny_unknown_fields)] 219 | pub struct ComponentConfig { 220 | /// The component id 221 | component: String, 222 | /// The topic 223 | topic: String, 224 | /// The QoS level 225 | qos: String, 226 | } 227 | 228 | /// Resolve variables in an expression against the variables in the provided trigger app. 229 | async fn resolve_variables( 230 | trigger_app: &TriggerApp, 231 | expr: String, 232 | ) -> anyhow::Result { 233 | match trigger_app.configured_app().app_state::() { 234 | Ok(variables) => anyhow::Ok(variables.resolve_expression(expr).await?), 235 | Err(spin_factors::Error::NoSuchFactor(_)) => Ok(expr), 236 | Err(err) => Err(err.into()), 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /examples/mqtt-app/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "android-tzdata" 7 | version = "0.1.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 10 | 11 | [[package]] 12 | name = "android_system_properties" 13 | version = "0.1.5" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 16 | dependencies = [ 17 | "libc", 18 | ] 19 | 20 | [[package]] 21 | name = "anyhow" 22 | version = "1.0.98" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 25 | 26 | [[package]] 27 | name = "autocfg" 28 | version = "1.1.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 31 | 32 | [[package]] 33 | name = "bitflags" 34 | version = "2.4.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 37 | 38 | [[package]] 39 | name = "bumpalo" 40 | version = "3.18.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" 43 | 44 | [[package]] 45 | name = "cc" 46 | version = "1.2.27" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" 49 | dependencies = [ 50 | "shlex", 51 | ] 52 | 53 | [[package]] 54 | name = "cfg-if" 55 | version = "1.0.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 58 | 59 | [[package]] 60 | name = "chrono" 61 | version = "0.4.34" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" 64 | dependencies = [ 65 | "android-tzdata", 66 | "iana-time-zone", 67 | "js-sys", 68 | "num-traits", 69 | "wasm-bindgen", 70 | "windows-targets", 71 | ] 72 | 73 | [[package]] 74 | name = "core-foundation-sys" 75 | version = "0.8.6" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 78 | 79 | [[package]] 80 | name = "equivalent" 81 | version = "1.0.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 84 | 85 | [[package]] 86 | name = "foldhash" 87 | version = "0.1.5" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 90 | 91 | [[package]] 92 | name = "futures" 93 | version = "0.3.30" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 96 | dependencies = [ 97 | "futures-channel", 98 | "futures-core", 99 | "futures-executor", 100 | "futures-io", 101 | "futures-sink", 102 | "futures-task", 103 | "futures-util", 104 | ] 105 | 106 | [[package]] 107 | name = "futures-channel" 108 | version = "0.3.30" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 111 | dependencies = [ 112 | "futures-core", 113 | "futures-sink", 114 | ] 115 | 116 | [[package]] 117 | name = "futures-core" 118 | version = "0.3.30" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 121 | 122 | [[package]] 123 | name = "futures-executor" 124 | version = "0.3.30" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 127 | dependencies = [ 128 | "futures-core", 129 | "futures-task", 130 | "futures-util", 131 | ] 132 | 133 | [[package]] 134 | name = "futures-io" 135 | version = "0.3.30" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 138 | 139 | [[package]] 140 | name = "futures-macro" 141 | version = "0.3.30" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 144 | dependencies = [ 145 | "proc-macro2", 146 | "quote", 147 | "syn 2.0.106", 148 | ] 149 | 150 | [[package]] 151 | name = "futures-sink" 152 | version = "0.3.30" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 155 | 156 | [[package]] 157 | name = "futures-task" 158 | version = "0.3.30" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 161 | 162 | [[package]] 163 | name = "futures-util" 164 | version = "0.3.30" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 167 | dependencies = [ 168 | "futures-channel", 169 | "futures-core", 170 | "futures-io", 171 | "futures-macro", 172 | "futures-sink", 173 | "futures-task", 174 | "memchr", 175 | "pin-project-lite", 176 | "pin-utils", 177 | "slab", 178 | ] 179 | 180 | [[package]] 181 | name = "hashbrown" 182 | version = "0.15.4" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" 185 | dependencies = [ 186 | "foldhash", 187 | ] 188 | 189 | [[package]] 190 | name = "heck" 191 | version = "0.5.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 194 | 195 | [[package]] 196 | name = "iana-time-zone" 197 | version = "0.1.60" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" 200 | dependencies = [ 201 | "android_system_properties", 202 | "core-foundation-sys", 203 | "iana-time-zone-haiku", 204 | "js-sys", 205 | "wasm-bindgen", 206 | "windows-core", 207 | ] 208 | 209 | [[package]] 210 | name = "iana-time-zone-haiku" 211 | version = "0.1.2" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 214 | dependencies = [ 215 | "cc", 216 | ] 217 | 218 | [[package]] 219 | name = "id-arena" 220 | version = "2.2.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 223 | 224 | [[package]] 225 | name = "indexmap" 226 | version = "2.9.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 229 | dependencies = [ 230 | "equivalent", 231 | "hashbrown", 232 | "serde", 233 | ] 234 | 235 | [[package]] 236 | name = "itoa" 237 | version = "1.0.9" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 240 | 241 | [[package]] 242 | name = "js-sys" 243 | version = "0.3.77" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 246 | dependencies = [ 247 | "once_cell", 248 | "wasm-bindgen", 249 | ] 250 | 251 | [[package]] 252 | name = "leb128fmt" 253 | version = "0.1.0" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" 256 | 257 | [[package]] 258 | name = "libc" 259 | version = "0.2.173" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" 262 | 263 | [[package]] 264 | name = "log" 265 | version = "0.4.20" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 268 | 269 | [[package]] 270 | name = "memchr" 271 | version = "2.7.1" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 274 | 275 | [[package]] 276 | name = "mqtt-app" 277 | version = "0.1.0" 278 | dependencies = [ 279 | "anyhow", 280 | "chrono", 281 | "spin-mqtt-sdk", 282 | ] 283 | 284 | [[package]] 285 | name = "num-traits" 286 | version = "0.2.18" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 289 | dependencies = [ 290 | "autocfg", 291 | ] 292 | 293 | [[package]] 294 | name = "once_cell" 295 | version = "1.19.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 298 | 299 | [[package]] 300 | name = "pin-project-lite" 301 | version = "0.2.13" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 304 | 305 | [[package]] 306 | name = "pin-utils" 307 | version = "0.1.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 310 | 311 | [[package]] 312 | name = "prettyplease" 313 | version = "0.2.37" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 316 | dependencies = [ 317 | "proc-macro2", 318 | "syn 2.0.106", 319 | ] 320 | 321 | [[package]] 322 | name = "proc-macro2" 323 | version = "1.0.95" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 326 | dependencies = [ 327 | "unicode-ident", 328 | ] 329 | 330 | [[package]] 331 | name = "quote" 332 | version = "1.0.35" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 335 | dependencies = [ 336 | "proc-macro2", 337 | ] 338 | 339 | [[package]] 340 | name = "rustversion" 341 | version = "1.0.21" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 344 | 345 | [[package]] 346 | name = "ryu" 347 | version = "1.0.15" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 350 | 351 | [[package]] 352 | name = "semver" 353 | version = "1.0.20" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" 356 | 357 | [[package]] 358 | name = "serde" 359 | version = "1.0.219" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 362 | dependencies = [ 363 | "serde_derive", 364 | ] 365 | 366 | [[package]] 367 | name = "serde_derive" 368 | version = "1.0.219" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 371 | dependencies = [ 372 | "proc-macro2", 373 | "quote", 374 | "syn 2.0.106", 375 | ] 376 | 377 | [[package]] 378 | name = "serde_json" 379 | version = "1.0.107" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 382 | dependencies = [ 383 | "itoa", 384 | "ryu", 385 | "serde", 386 | ] 387 | 388 | [[package]] 389 | name = "shlex" 390 | version = "1.3.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 393 | 394 | [[package]] 395 | name = "slab" 396 | version = "0.4.9" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 399 | dependencies = [ 400 | "autocfg", 401 | ] 402 | 403 | [[package]] 404 | name = "spin-executor" 405 | version = "5.1.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "9fa74c56ad71afb64fff0566d7cb313567d0469ba2a75b0af58c323f59b74280" 408 | dependencies = [ 409 | "futures", 410 | "once_cell", 411 | "wasi", 412 | ] 413 | 414 | [[package]] 415 | name = "spin-mqtt-macro" 416 | version = "0.2.0" 417 | dependencies = [ 418 | "proc-macro2", 419 | "quote", 420 | "syn 1.0.109", 421 | "wit-bindgen", 422 | ] 423 | 424 | [[package]] 425 | name = "spin-mqtt-sdk" 426 | version = "0.2.0" 427 | dependencies = [ 428 | "spin-executor", 429 | "spin-mqtt-macro", 430 | "wit-bindgen", 431 | ] 432 | 433 | [[package]] 434 | name = "syn" 435 | version = "1.0.109" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 438 | dependencies = [ 439 | "proc-macro2", 440 | "quote", 441 | "unicode-ident", 442 | ] 443 | 444 | [[package]] 445 | name = "syn" 446 | version = "2.0.106" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 449 | dependencies = [ 450 | "proc-macro2", 451 | "quote", 452 | "unicode-ident", 453 | ] 454 | 455 | [[package]] 456 | name = "unicode-ident" 457 | version = "1.0.12" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 460 | 461 | [[package]] 462 | name = "unicode-xid" 463 | version = "0.2.4" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 466 | 467 | [[package]] 468 | name = "wasi" 469 | version = "0.13.1+wasi-0.2.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "2f43d1c36145feb89a3e61aa0ba3e582d976a8ab77f1474aa0adb80800fe0cf8" 472 | dependencies = [ 473 | "wit-bindgen-rt 0.24.0", 474 | ] 475 | 476 | [[package]] 477 | name = "wasm-bindgen" 478 | version = "0.2.100" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 481 | dependencies = [ 482 | "cfg-if", 483 | "once_cell", 484 | "rustversion", 485 | "wasm-bindgen-macro", 486 | ] 487 | 488 | [[package]] 489 | name = "wasm-bindgen-backend" 490 | version = "0.2.100" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 493 | dependencies = [ 494 | "bumpalo", 495 | "log", 496 | "proc-macro2", 497 | "quote", 498 | "syn 2.0.106", 499 | "wasm-bindgen-shared", 500 | ] 501 | 502 | [[package]] 503 | name = "wasm-bindgen-macro" 504 | version = "0.2.100" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 507 | dependencies = [ 508 | "quote", 509 | "wasm-bindgen-macro-support", 510 | ] 511 | 512 | [[package]] 513 | name = "wasm-bindgen-macro-support" 514 | version = "0.2.100" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 517 | dependencies = [ 518 | "proc-macro2", 519 | "quote", 520 | "syn 2.0.106", 521 | "wasm-bindgen-backend", 522 | "wasm-bindgen-shared", 523 | ] 524 | 525 | [[package]] 526 | name = "wasm-bindgen-shared" 527 | version = "0.2.100" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 530 | dependencies = [ 531 | "unicode-ident", 532 | ] 533 | 534 | [[package]] 535 | name = "wasm-encoder" 536 | version = "0.235.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "b3bc393c395cb621367ff02d854179882b9a351b4e0c93d1397e6090b53a5c2a" 539 | dependencies = [ 540 | "leb128fmt", 541 | "wasmparser", 542 | ] 543 | 544 | [[package]] 545 | name = "wasm-metadata" 546 | version = "0.235.0" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "b055604ba04189d54b8c0ab2c2fc98848f208e103882d5c0b984f045d5ea4d20" 549 | dependencies = [ 550 | "anyhow", 551 | "indexmap", 552 | "wasm-encoder", 553 | "wasmparser", 554 | ] 555 | 556 | [[package]] 557 | name = "wasmparser" 558 | version = "0.235.0" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "161296c618fa2d63f6ed5fffd1112937e803cb9ec71b32b01a76321555660917" 561 | dependencies = [ 562 | "bitflags", 563 | "hashbrown", 564 | "indexmap", 565 | "semver", 566 | ] 567 | 568 | [[package]] 569 | name = "windows-core" 570 | version = "0.52.0" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 573 | dependencies = [ 574 | "windows-targets", 575 | ] 576 | 577 | [[package]] 578 | name = "windows-targets" 579 | version = "0.52.6" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 582 | dependencies = [ 583 | "windows_aarch64_gnullvm", 584 | "windows_aarch64_msvc", 585 | "windows_i686_gnu", 586 | "windows_i686_gnullvm", 587 | "windows_i686_msvc", 588 | "windows_x86_64_gnu", 589 | "windows_x86_64_gnullvm", 590 | "windows_x86_64_msvc", 591 | ] 592 | 593 | [[package]] 594 | name = "windows_aarch64_gnullvm" 595 | version = "0.52.6" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 598 | 599 | [[package]] 600 | name = "windows_aarch64_msvc" 601 | version = "0.52.6" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 604 | 605 | [[package]] 606 | name = "windows_i686_gnu" 607 | version = "0.52.6" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 610 | 611 | [[package]] 612 | name = "windows_i686_gnullvm" 613 | version = "0.52.6" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 616 | 617 | [[package]] 618 | name = "windows_i686_msvc" 619 | version = "0.52.6" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 622 | 623 | [[package]] 624 | name = "windows_x86_64_gnu" 625 | version = "0.52.6" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 628 | 629 | [[package]] 630 | name = "windows_x86_64_gnullvm" 631 | version = "0.52.6" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 634 | 635 | [[package]] 636 | name = "windows_x86_64_msvc" 637 | version = "0.52.6" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 640 | 641 | [[package]] 642 | name = "wit-bindgen" 643 | version = "0.43.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "9a18712ff1ec5bd09da500fe1e91dec11256b310da0ff33f8b4ec92b927cf0c6" 646 | dependencies = [ 647 | "wit-bindgen-rt 0.43.0", 648 | "wit-bindgen-rust-macro", 649 | ] 650 | 651 | [[package]] 652 | name = "wit-bindgen-core" 653 | version = "0.43.0" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "2c53468e077362201de11999c85c07c36e12048a990a3e0d69da2bd61da355d0" 656 | dependencies = [ 657 | "anyhow", 658 | "heck", 659 | "wit-parser", 660 | ] 661 | 662 | [[package]] 663 | name = "wit-bindgen-rt" 664 | version = "0.24.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "3b0780cf7046630ed70f689a098cd8d56c5c3b22f2a7379bbdb088879963ff96" 667 | dependencies = [ 668 | "bitflags", 669 | ] 670 | 671 | [[package]] 672 | name = "wit-bindgen-rt" 673 | version = "0.43.0" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "9fd734226eac1fd7c450956964e3a9094c9cee65e9dafdf126feef8c0096db65" 676 | dependencies = [ 677 | "bitflags", 678 | "futures", 679 | "once_cell", 680 | ] 681 | 682 | [[package]] 683 | name = "wit-bindgen-rust" 684 | version = "0.43.0" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "531ebfcec48e56473805285febdb450e270fa75b2dacb92816861d0473b4c15f" 687 | dependencies = [ 688 | "anyhow", 689 | "heck", 690 | "indexmap", 691 | "prettyplease", 692 | "syn 2.0.106", 693 | "wasm-metadata", 694 | "wit-bindgen-core", 695 | "wit-component", 696 | ] 697 | 698 | [[package]] 699 | name = "wit-bindgen-rust-macro" 700 | version = "0.43.0" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "7852bf8a9d1ea80884d26b864ddebd7b0c7636697c6ca10f4c6c93945e023966" 703 | dependencies = [ 704 | "anyhow", 705 | "prettyplease", 706 | "proc-macro2", 707 | "quote", 708 | "syn 2.0.106", 709 | "wit-bindgen-core", 710 | "wit-bindgen-rust", 711 | ] 712 | 713 | [[package]] 714 | name = "wit-component" 715 | version = "0.235.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "64a57a11109cc553396f89f3a38a158a97d0b1adaec113bd73e0f64d30fb601f" 718 | dependencies = [ 719 | "anyhow", 720 | "bitflags", 721 | "indexmap", 722 | "log", 723 | "serde", 724 | "serde_derive", 725 | "serde_json", 726 | "wasm-encoder", 727 | "wasm-metadata", 728 | "wasmparser", 729 | "wit-parser", 730 | ] 731 | 732 | [[package]] 733 | name = "wit-parser" 734 | version = "0.235.0" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "0a1f95a87d03a33e259af286b857a95911eb46236a0f726cbaec1227b3dfc67a" 737 | dependencies = [ 738 | "anyhow", 739 | "id-arena", 740 | "indexmap", 741 | "log", 742 | "semver", 743 | "serde", 744 | "serde_derive", 745 | "serde_json", 746 | "unicode-xid", 747 | "wasmparser", 748 | ] 749 | --------------------------------------------------------------------------------