Rust Assertion Example
This example demonstrates how to write a custom assertion in Rust for a Juno serverless function. It shows how to intercept and validate data operations—such as rejecting specific content—before it's written to the datastore.
The project includes a minimal frontend to help trigger and test the logic, but the primary focus is the backend assertion.
You can browse the source code here: github.com/junobuild/examples/tree/main/functions/rust/assertions
Folder Structure
rust/assertions/
├── src/
│ ├── satellite/ # Rust Satellite serverless function
│ │ ├── src/
│ │ │ └── lib.rs # Main Rust logic for Satellite
│ │ ├── satellite.did # Candid interface definition
│ │ └── Cargo.toml # Rust package config
├── src/components/ # Minimal frontend React components
├── juno.config.ts # Juno Satellite configuration
├── package.json # Frontend dependencies
└── ...
Key Features
- Custom Assertions in Rust: Demonstrates how to reject or validate data before it's saved, using Rust serverless functions.
- Serverless Integration: Runs as a Satellite function and integrates with Juno's datastore and authentication system.
- Minimal UI for Testing: A simple frontend is included to test and demonstrate the assertion logic in action.
Main Backend Components
- src/satellite/src/lib.rs: The core Rust logic for the Satellite serverless function. Implements the custom assertions (e.g., only allow certain valid inputs, etc.).
- src/satellite/Cargo.toml: Rust package configuration for the Satellite function.
Example: Custom Assertion in Rust
Here’s the actual Rust logic from lib.rs
:
// This example defines a custom assertion in a Juno Satellite using Rust.
// It checks if a document being saved to the "notes" collection contains the word "hello".
// If it does, the assertion rejects the operation and logs a message.
use ic_cdk::print;
use junobuild_macros::assert_set_doc;
use junobuild_satellite::{include_satellite, AssertSetDocContext};
use junobuild_utils::decode_doc_data;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Note {
text: String,
url: Option<String>,
}
#[assert_set_doc(collections = ["notes"])]
fn assert_set_doc(context: AssertSetDocContext) -> Result<(), String> {
let note = decode_doc_data::<Note>(&context.data.data.proposed.data)?;
if note.text.to_lowercase().contains("hello") {
print(format!("❌ Rejected note containing 'hello': {}", note.text));
return Err("The note should not contain the keyword 'hello'.".to_string());
}
print(format!("✅ Note accepted: {}", note.text));
Ok(())
}
include_satellite!();
Explanation:
- Defines a
Note
struct withtext
and optionalurl
fields. Similar as the fields used in the frontend. - Uses the
#[assert_set_doc]
macro to create a custom assertion for thenotes
collection. - When a note is created or updated, the assertion checks if the note's text contains the word "hello" (case-insensitive).
- If it does, the note is rejected and an error message is returned; otherwise, the note is accepted.
- Prints a message to the log for both accepted and rejected notes.
include_satellite!();
brings in the necessary boilerplate for the Juno Satellite runtime.
How to Run
- Clone the repo:
git clone https://github.com/junobuild/examples
cd rust/assertions
2. Install dependencies:
npm install
3. Start Juno local emulator:
Requires the Juno CLI to be available npm i -g @junobuild/cli
juno dev start
4. Create a Satellite for local dev:
- Visit http://localhost:5866 and follow the instructions.
- Update
juno.config.ts
with your Satellite ID.
- Create required collections:
notes
in Datastore: http://localhost:5866/datastoreimages
in Storage: http://localhost:5866/storage
- Start the frontend dev server (in a separate terminal):
npm run dev
- Build the serverless functions (in a separate terminal):
juno functions build
The emulator will automatically upgrade your Satellite and live reload the changes.
Juno-Specific Configuration
- juno.config.ts: Defines Satellite IDs for development/production, build source, and predeploy steps. See the Configuration reference for details.
- vite.config.ts: Registers the
juno
plugin to inject environment variables automatically. See the Vite Plugin reference for more information.
Production Deployment
- Create a Satellite on the Juno Console for mainnet.
- Update
juno.config.ts
with the production Satellite ID. - Build and deploy the frontend:
npm run build
juno deploy
- Build and upgrade the serverless functions:
juno functions build
juno functions upgrade
Notes
- This example focuses on the Rust serverless function; the frontend is intentionally minimal and only included for demonstration purposes.
- Use this project as a starting point for writing custom assertions and backend logic in Rust with Juno.
Real-World Example
Want to see how assertions and serverless logic are used in a live project?
Check out proposals.network, an open-source app built with Juno:
- GitHub: github.com/peterpeterparker/proposals.network
- Example logic: src/satellite/src/lib.rs
This app uses:
#[on_delete_doc]
and#[assert_delete_doc]
to validate and clean up related documents and assets- Shared helper modules like
assert
,delete
, andtypes
to keep logic organized - A real-world pattern of chaining asset/document deletions with assertions
It’s a great reference for more advanced setups and multi-collection coordination.
References
- Serverless Functions Guide
- Functions Development
- Rust SDK Reference
- Rust Utils Reference
- Run Local Development
- CLI Reference
- Configuration Reference
- Datastore Collections
Crate Docs
These crates are used to build and extend serverless functions in Rust with Juno:
- junobuild-satellite: Core features and runtime for building a Satellite in Rust, including hooks, assertions, and datastore integration.
- junobuild-macros: Procedural macros for declaratively attaching hooks and assertions.
- junobuild-utils: Utility helpers for working with documents, including data encoding, decoding, and assertion context handling.
- junobuild-shared: Shared types and helpers for Juno projects. Used by all containers including the Console.
- junobuild-storage: Storage helpers for working with assets and HTTP headers in Juno.