From 633e2a806e02b811dd76b220da185abd7434b5aa Mon Sep 17 00:00:00 2001 From: Andrew Lauer Date: Mon, 1 Jun 2026 15:55:13 -0700 Subject: [PATCH 1/5] Use validator crate --- dimos/mapping/ray_tracing/rust/Cargo.lock | 391 ++++++++++++++++++ dimos/mapping/ray_tracing/rust/Cargo.toml | 1 + dimos/mapping/ray_tracing/rust/src/main.rs | 62 +-- examples/native-modules/rust/Cargo.lock | 391 ++++++++++++++++++ examples/native-modules/rust/Cargo.toml | 1 + .../native-modules/rust/src/native_pong.rs | 4 +- native/rust/Cargo.lock | 390 +++++++++++++++++ native/rust/README.md | 35 +- native/rust/dimos-module-macros/src/lib.rs | 7 +- native/rust/dimos-module/Cargo.toml | 1 + native/rust/dimos-module/src/lib.rs | 2 +- native/rust/dimos-module/src/module.rs | 63 ++- 12 files changed, 1295 insertions(+), 53 deletions(-) diff --git a/dimos/mapping/ray_tracing/rust/Cargo.lock b/dimos/mapping/ray_tracing/rust/Cargo.lock index 523971cdc6..fa22843fd3 100644 --- a/dimos/mapping/ray_tracing/rust/Cargo.lock +++ b/dimos/mapping/ray_tracing/rust/Cargo.lock @@ -42,6 +42,41 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "dimos-lcm" version = "0.1.0" @@ -63,6 +98,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", + "validator", ] [[package]] @@ -84,6 +120,18 @@ dependencies = [ "serde", "tokio", "tracing", + "validator", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -96,6 +144,21 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -108,6 +171,115 @@ dependencies = [ "wasip2", ] +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "itoa" version = "1.0.18" @@ -134,6 +306,12 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "log" version = "0.4.29" @@ -181,12 +359,49 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -211,6 +426,18 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.14" @@ -316,6 +543,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.117" @@ -327,6 +566,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -336,6 +586,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.52.3" @@ -443,6 +703,54 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "validator" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "valuable" version = "0.1.1" @@ -564,6 +872,35 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.48" @@ -584,6 +921,60 @@ dependencies = [ "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/dimos/mapping/ray_tracing/rust/Cargo.toml b/dimos/mapping/ray_tracing/rust/Cargo.toml index 88fcc3cf4e..5f329637ef 100644 --- a/dimos/mapping/ray_tracing/rust/Cargo.toml +++ b/dimos/mapping/ray_tracing/rust/Cargo.toml @@ -16,6 +16,7 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] } serde = { version = "1", features = ["derive"] } ahash = "0.8" tracing = "0.1" +validator = { version = "0.20", features = ["derive"] } [profile.release] lto = "thin" diff --git a/dimos/mapping/ray_tracing/rust/src/main.rs b/dimos/mapping/ray_tracing/rust/src/main.rs index e5a010b8d8..c552ca000a 100644 --- a/dimos/mapping/ray_tracing/rust/src/main.rs +++ b/dimos/mapping/ray_tracing/rust/src/main.rs @@ -9,21 +9,36 @@ use lcm_msgs::nav_msgs::Odometry; use lcm_msgs::sensor_msgs::{PointCloud2, PointField}; use lcm_msgs::std_msgs::{Header, Time}; use serde::Deserialize; +use validator::{Validate, ValidationError}; type VoxelKey = (i32, i32, i32); -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Validate)] #[serde(deny_unknown_fields)] +#[validate(schema(function = "validate_health_range"))] struct Config { + #[validate(range(exclusive_min = 0.0))] voxel_size: f32, + #[validate(range(min = 0.0))] max_range: f32, + #[validate(range(min = 1))] ray_subsample: u32, + #[validate(range(min = 0.0))] shadow_depth: f32, + #[validate(range(min = 0.0))] grace_depth: f32, min_health: i32, + #[validate(range(min = 1))] max_health: i32, } +fn validate_health_range(cfg: &Config) -> Result<(), ValidationError> { + if cfg.min_health >= cfg.max_health { + return Err(ValidationError::new("min_health_lt_max_health")); + } + Ok(()) +} + #[derive(Default)] struct VoxelMap { // Save health of each voxel @@ -39,7 +54,6 @@ struct LocalBounds { } #[derive(Module)] -#[module(setup = validate_config)] struct RayTracingVoxelMap { #[input(decode = PointCloud2::decode, handler = on_lidar)] lidar: Input, @@ -61,50 +75,6 @@ struct RayTracingVoxelMap { } impl RayTracingVoxelMap { - /// Make sure all the configs are valid on setup - async fn validate_config(&self) { - let cfg = &self.config; - if !cfg.voxel_size.is_finite() || cfg.voxel_size <= 0.0 { - panic!( - "voxel_ray_tracing: voxel_size must be > 0, got {}", - cfg.voxel_size - ); - } - if !cfg.max_range.is_finite() || cfg.max_range < 0.0 { - panic!( - "voxel_ray_tracing: max_range must be >= 0, got {}", - cfg.max_range - ); - } - if !cfg.shadow_depth.is_finite() || cfg.shadow_depth < 0.0 { - panic!( - "voxel_ray_tracing: shadow_depth must be >= 0, got {}", - cfg.shadow_depth - ); - } - if !cfg.grace_depth.is_finite() || cfg.grace_depth < 0.0 { - panic!( - "voxel_ray_tracing: grace_depth must be >= 0, got {}", - cfg.grace_depth - ); - } - if cfg.ray_subsample == 0 { - panic!("voxel_ray_tracing: ray_subsample must be >= 1, got 0"); - } - if cfg.max_health <= 0 { - panic!( - "voxel_ray_tracing: max_health must be > 0 or voxels can never become visible, got {}", - cfg.max_health - ); - } - if cfg.min_health >= cfg.max_health { - panic!( - "voxel_ray_tracing: min_health ({}) must be < max_health ({})", - cfg.min_health, cfg.max_health - ); - } - } - async fn on_odometry(&mut self, msg: Odometry) { self.last_origin = Some(( msg.pose.pose.position.x as f32, diff --git a/examples/native-modules/rust/Cargo.lock b/examples/native-modules/rust/Cargo.lock index 3e38617bf8..8a7fda6bba 100644 --- a/examples/native-modules/rust/Cargo.lock +++ b/examples/native-modules/rust/Cargo.lock @@ -29,6 +29,41 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "dimos-lcm" version = "0.1.0" @@ -50,6 +85,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", + "validator", ] [[package]] @@ -70,6 +106,18 @@ dependencies = [ "serde", "tokio", "tracing", + "validator", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -82,6 +130,130 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "itoa" version = "1.0.18" @@ -108,6 +280,12 @@ version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "log" version = "0.4.29" @@ -155,12 +333,49 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -179,6 +394,18 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.14" @@ -284,6 +511,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.117" @@ -295,6 +534,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -304,6 +554,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.52.1" @@ -411,6 +671,54 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "validator" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "valuable" version = "0.1.1" @@ -511,6 +819,89 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/examples/native-modules/rust/Cargo.toml b/examples/native-modules/rust/Cargo.toml index 1c406fc947..4df2cf25f7 100644 --- a/examples/native-modules/rust/Cargo.toml +++ b/examples/native-modules/rust/Cargo.toml @@ -17,3 +17,4 @@ lcm-msgs = { git = "https://github.com/dimensionalOS/dimos-lcm.git", branch = "r tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] } serde = { version = "1", features = ["derive"] } tracing = "0.1" +validator = { version = "0.20", features = ["derive"] } diff --git a/examples/native-modules/rust/src/native_pong.rs b/examples/native-modules/rust/src/native_pong.rs index 36f33dc44f..f39fbee896 100644 --- a/examples/native-modules/rust/src/native_pong.rs +++ b/examples/native-modules/rust/src/native_pong.rs @@ -1,10 +1,12 @@ use dimos_module::{run, Input, LcmTransport, Module, Output}; use lcm_msgs::geometry_msgs::{Twist, Vector3}; use serde::Deserialize; +use validator::Validate; -#[derive(Debug, Deserialize, Default)] +#[derive(Debug, Deserialize, Default, Validate)] #[serde(deny_unknown_fields)] struct PongConfig { + #[validate(range(min = 0, max = 1000))] sample_config: i64, } diff --git a/native/rust/Cargo.lock b/native/rust/Cargo.lock index 5d8960bf75..165d4b923f 100644 --- a/native/rust/Cargo.lock +++ b/native/rust/Cargo.lock @@ -29,6 +29,41 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "dimos-lcm" version = "0.1.0" @@ -52,6 +87,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-test", + "validator", ] [[package]] @@ -63,6 +99,17 @@ dependencies = [ "syn", ] +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "errno" version = "0.3.14" @@ -73,6 +120,130 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "itoa" version = "1.0.18" @@ -99,6 +270,12 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "log" version = "0.4.29" @@ -146,12 +323,49 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -170,6 +384,18 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.14" @@ -275,6 +501,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.117" @@ -286,6 +524,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -295,6 +544,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.52.3" @@ -423,6 +682,54 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "validator" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "valuable" version = "0.1.1" @@ -523,6 +830,89 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/native/rust/README.md b/native/rust/README.md index cd85b0d1d4..5a4064388d 100644 --- a/native/rust/README.md +++ b/native/rust/README.md @@ -11,9 +11,13 @@ Two crates: use dimos_module::{run, Input, LcmTransport, Module, Output}; use lcm_msgs::geometry_msgs::Twist; use serde::Deserialize; +use validator::Validate; -#[derive(Debug, Deserialize, Default)] -struct MyConfig { threshold: f64 } +#[derive(Debug, Deserialize, Default, Validate)] +struct MyConfig { + #[validate(range(min = 0.0))] + threshold: f64, +} #[derive(Module)] #[module(setup = on_start, teardown = on_stop)] @@ -52,9 +56,34 @@ async fn main() { - `#[module(setup = fn, teardown = fn)]`: on the struct. Both optional. Names methods on `Self`. `setup` runs once before the input dispatch loop starts (use it to spawn background tasks or initialize resources); `teardown` runs once after the loop exits (use it for cleanup). - `#[input(decode = fn, handler = fn)]`: on a field of type `Input`. `decode` is required; `handler` defaults to `handle_`. - `#[output(encode = fn)]`: on a field of type `Output`. `encode` is required. -- `#[config]`: on one field of any `Deserialize` type. At most one per struct. If absent, `Config = ()`. +- `#[config]`: on one field. The type must derive `Deserialize + Debug + Validate` (from the [`validator`](https://docs.rs/validator) crate, which the module crate must depend on directly). At most one per struct. If absent, `Config` defaults to `dimos_module::NoConfig`. - Unattributed fields are initialized via `Default::default()` and treated as module state. +## Config validation + +`run()` calls `config.validate()` after deserializing and bails with an `io::Error` on failure. The attributes you'll usually reach for are `range` for numbers, `length` or `regex` for strings, and `custom(function = ...)` for one-off rules. Use `#[validate(schema(function = "..."))]` on the struct for cross-field invariants. + +```rust +use validator::{Validate, ValidationError}; + +#[derive(Debug, Deserialize, Validate)] +#[validate(schema(function = "validate_health_range"))] +struct Config { + #[validate(range(exclusive_min = 0.0))] + voxel_size: f32, + #[validate(range(min = 1))] + max_health: i32, + min_health: i32, +} + +fn validate_health_range(cfg: &Config) -> Result<(), ValidationError> { + if cfg.min_health >= cfg.max_health { + return Err(ValidationError::new("min_health_lt_max_health")); + } + Ok(()) +} +``` + Field name = port name. Ports map to topics via the stdin JSON; unmapped ports fall back to `/{port}`. ## What `#[derive(Module)]` generates diff --git a/native/rust/dimos-module-macros/src/lib.rs b/native/rust/dimos-module-macros/src/lib.rs index 184e39c75e..ecfb47c735 100644 --- a/native/rust/dimos-module-macros/src/lib.rs +++ b/native/rust/dimos-module-macros/src/lib.rs @@ -92,7 +92,7 @@ fn expand(input: DeriveInput) -> syn::Result { let config_type: Type = classified .iter() .find_map(|f| matches!(f.kind, FieldKind::Config).then(|| f.ty.clone())) - .unwrap_or_else(|| syn::parse_quote!(())); + .unwrap_or_else(|| syn::parse_quote!(::dimos_module::NoConfig)); let config_param: TokenStream2 = if config_seen.is_some() { quote!(config) @@ -181,6 +181,11 @@ fn expand(input: DeriveInput) -> syn::Result { #teardown_impl } + + const _: fn() = || { + fn assert_module_config() {} + assert_module_config::<#config_type>(); + }; }) } diff --git a/native/rust/dimos-module/Cargo.toml b/native/rust/dimos-module/Cargo.toml index 9e5771448c..b695a077a8 100644 --- a/native/rust/dimos-module/Cargo.toml +++ b/native/rust/dimos-module/Cargo.toml @@ -13,6 +13,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["json", "env-filter"] } +validator = { version = "0.20", features = ["derive"] } [dev-dependencies] lcm-msgs = { git = "https://github.com/dimensionalOS/dimos-lcm.git", branch = "rust-codegen" } diff --git a/native/rust/dimos-module/src/lib.rs b/native/rust/dimos-module/src/lib.rs index b9ef0a95e8..8c69bea05d 100644 --- a/native/rust/dimos-module/src/lib.rs +++ b/native/rust/dimos-module/src/lib.rs @@ -5,7 +5,7 @@ pub mod transport; pub use dimos_module_macros::Module; pub use lcm::LcmTransport; -pub use module::{run, Builder, Input, Module, Output}; +pub use module::{run, Builder, Input, Module, ModuleConfig, NoConfig, Output}; pub use transport::Transport; // Re-export LcmOptions so callers don't need to depend on dimos-lcm directly. diff --git a/native/rust/dimos-module/src/module.rs b/native/rust/dimos-module/src/module.rs index 408e8087ee..4ba5b2f91c 100644 --- a/native/rust/dimos-module/src/module.rs +++ b/native/rust/dimos-module/src/module.rs @@ -10,9 +10,28 @@ use tracing::{error, info, warn}; use tracing_subscriber::EnvFilter; use serde::de::DeserializeOwned; +use serde::Deserialize; +use validator::Validate; use crate::transport::Transport; +/// Trait required by `Module::Config`s to ensure that configurations are +/// validated correctly. +pub trait ModuleConfig: DeserializeOwned + Debug + Validate {} +impl ModuleConfig for T {} + +/// Default config type used by `#[derive(Module)]` when no `#[config]` field +/// is present. Just a stand in for modules that don't use configurations. +#[derive(Debug, Default, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct NoConfig; + +impl Validate for NoConfig { + fn validate(&self) -> Result<(), validator::ValidationErrors> { + Ok(()) + } +} + fn init_tracing() { let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); let _ = tracing_subscriber::fmt() @@ -125,8 +144,17 @@ fn parse_config_json(line: &str) -> io::Result<(HashMap(config: &C) -> io::Result<()> { + config.validate().map_err(|errs| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("config validation failed: {errs}"), + ) + }) +} + pub trait Module: Sized + Send + 'static { - type Config: DeserializeOwned + Debug; + type Config: ModuleConfig; fn build(builder: &mut Builder, config: Self::Config) -> Self; @@ -255,6 +283,7 @@ where .read_line(&mut line) .await?; let (topics, config) = parse_config_json::(&line)?; + validate_config(&config)?; let exe = std::env::current_exe() .ok() @@ -447,6 +476,38 @@ mod tests { assert!(result.is_err()); } + // validate_config + + #[derive(Debug, Deserialize, Validate)] + struct RangedConfig { + #[validate(range(min = 1, max = 10))] + value: i64, + } + + #[test] + fn validate_config_passes_when_in_range() { + let cfg = RangedConfig { value: 5 }; + assert!(validate_config(&cfg).is_ok()); + } + + #[test] + fn validate_config_returns_invalid_data_when_out_of_range() { + let cfg = RangedConfig { value: 0 }; + let err = validate_config(&cfg).expect_err("expected validation failure"); + assert_eq!(err.kind(), io::ErrorKind::InvalidData); + let msg = err.to_string(); + assert!(msg.contains("value"), "error should name the field: {msg}"); + assert!( + msg.contains("config validation failed"), + "error should be framed: {msg}", + ); + } + + #[test] + fn empty_config_validates() { + assert!(validate_config(&crate::module::NoConfig).is_ok()); + } + // topic_for fallback fn topics(pairs: &[(&str, &str)]) -> HashMap { From def6e573cbb026ec789018a6b3eba066096b558f Mon Sep 17 00:00:00 2001 From: Andrew Lauer Date: Mon, 1 Jun 2026 16:06:47 -0700 Subject: [PATCH 2/5] Remove unused code --- native/rust/dimos-module-macros/src/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/native/rust/dimos-module-macros/src/lib.rs b/native/rust/dimos-module-macros/src/lib.rs index ecfb47c735..4bed5ccbbd 100644 --- a/native/rust/dimos-module-macros/src/lib.rs +++ b/native/rust/dimos-module-macros/src/lib.rs @@ -181,11 +181,6 @@ fn expand(input: DeriveInput) -> syn::Result { #teardown_impl } - - const _: fn() = || { - fn assert_module_config() {} - assert_module_config::<#config_type>(); - }; }) } From 4079b715787693d07f43c52ca800bff99e6f6b42 Mon Sep 17 00:00:00 2001 From: Andrew Lauer Date: Mon, 1 Jun 2026 16:22:53 -0700 Subject: [PATCH 3/5] Better error message and handling --- dimos/mapping/ray_tracing/rust/src/main.rs | 4 +- .../native-modules/rust/src/native_ping.rs | 2 +- .../native-modules/rust/src/native_pong.rs | 2 +- native/rust/README.md | 2 +- native/rust/dimos-module/src/module.rs | 61 ++++++++++++++++++- 5 files changed, 63 insertions(+), 8 deletions(-) diff --git a/dimos/mapping/ray_tracing/rust/src/main.rs b/dimos/mapping/ray_tracing/rust/src/main.rs index c552ca000a..5bc65181d8 100644 --- a/dimos/mapping/ray_tracing/rust/src/main.rs +++ b/dimos/mapping/ray_tracing/rust/src/main.rs @@ -540,9 +540,7 @@ async fn main() { let transport = LcmTransport::new() .await .expect("failed to create LCM transport"); - run::(transport) - .await - .expect("voxel_ray_tracing run failed"); + run::(transport).await; } #[cfg(test)] diff --git a/examples/native-modules/rust/src/native_ping.rs b/examples/native-modules/rust/src/native_ping.rs index 479b402a25..d872f98e92 100644 --- a/examples/native-modules/rust/src/native_ping.rs +++ b/examples/native-modules/rust/src/native_ping.rs @@ -52,5 +52,5 @@ async fn main() { let transport = LcmTransport::new() .await .expect("Failed to create transport"); - run::(transport).await.expect("ping run failed"); + run::(transport).await; } diff --git a/examples/native-modules/rust/src/native_pong.rs b/examples/native-modules/rust/src/native_pong.rs index f39fbee896..a488824751 100644 --- a/examples/native-modules/rust/src/native_pong.rs +++ b/examples/native-modules/rust/src/native_pong.rs @@ -41,5 +41,5 @@ async fn main() { let transport = LcmTransport::new() .await .expect("Failed to create transport"); - run::(transport).await.expect("pong run failed"); + run::(transport).await; } diff --git a/native/rust/README.md b/native/rust/README.md index 5a4064388d..df1a035733 100644 --- a/native/rust/README.md +++ b/native/rust/README.md @@ -46,7 +46,7 @@ impl MyModule { #[tokio::main] async fn main() { let transport = LcmTransport::new().await.unwrap(); - run::(transport).await.unwrap(); + run::(transport).await; } ``` diff --git a/native/rust/dimos-module/src/module.rs b/native/rust/dimos-module/src/module.rs index 4ba5b2f91c..32040497f4 100644 --- a/native/rust/dimos-module/src/module.rs +++ b/native/rust/dimos-module/src/module.rs @@ -144,11 +144,57 @@ fn parse_config_json(line: &str) -> io::Result<(HashMap String { + use validator::ValidationErrorsKind; + let mut messages = Vec::new(); + for (field, kind) in errors.errors() { + match kind { + ValidationErrorsKind::Field(field_errs) => { + for err in field_errs { + let mut bounds: Vec = err + .params + .iter() + .filter(|(k, _)| k.as_ref() != "value") + .map(|(k, v)| format!("{k}={v}")) + .collect(); + bounds.sort(); + let bounds_str = if bounds.is_empty() { + String::new() + } else { + format!(" ({})", bounds.join(", ")) + }; + let got = err + .params + .get("value") + .map(|v| format!(" got {v}")) + .unwrap_or_default(); + messages.push(format!("{field}: {}{bounds_str}{got}", err.code)); + } + } + ValidationErrorsKind::Struct(nested) => { + messages.push(format!("{field}: {}", format_validation_errors(nested))); + } + ValidationErrorsKind::List(list) => { + for (idx, errs) in list { + messages.push(format!( + "{field}[{idx}]: {}", + format_validation_errors(errs) + )); + } + } + } + } + messages.join("; ") +} + fn validate_config(config: &C) -> io::Result<()> { config.validate().map_err(|errs| { io::Error::new( io::ErrorKind::InvalidData, - format!("config validation failed: {errs}"), + format!( + "config validation failed: {}", + format_validation_errors(&errs) + ), ) }) } @@ -271,7 +317,18 @@ fn propagate_task_failure(name: &str, res: Result<(), tokio::task::JoinError>) { } } -pub async fn run(transport: T) -> io::Result<()> +pub async fn run(transport: T) +where + M: Module, + T: Transport, +{ + if let Err(e) = run_fallible::(transport).await { + error!("{e}"); + std::process::exit(1); + } +} + +async fn run_fallible(transport: T) -> io::Result<()> where M: Module, T: Transport, From 4c11c707153c71f1f4b25e7a65b0975ec9b3d952 Mon Sep 17 00:00:00 2001 From: Andrew Lauer Date: Mon, 1 Jun 2026 16:26:13 -0700 Subject: [PATCH 4/5] Better comments --- native/rust/README.md | 4 ++-- native/rust/dimos-module/src/module.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/native/rust/README.md b/native/rust/README.md index df1a035733..a3bf1e6aba 100644 --- a/native/rust/README.md +++ b/native/rust/README.md @@ -56,12 +56,12 @@ async fn main() { - `#[module(setup = fn, teardown = fn)]`: on the struct. Both optional. Names methods on `Self`. `setup` runs once before the input dispatch loop starts (use it to spawn background tasks or initialize resources); `teardown` runs once after the loop exits (use it for cleanup). - `#[input(decode = fn, handler = fn)]`: on a field of type `Input`. `decode` is required; `handler` defaults to `handle_`. - `#[output(encode = fn)]`: on a field of type `Output`. `encode` is required. -- `#[config]`: on one field. The type must derive `Deserialize + Debug + Validate` (from the [`validator`](https://docs.rs/validator) crate, which the module crate must depend on directly). At most one per struct. If absent, `Config` defaults to `dimos_module::NoConfig`. +- `#[config]`: on one field. The type must derive `Deserialize + Debug + Validate` (from the [`validator`](https://docs.rs/validator) crate). At most one per struct. If absent, `Config` defaults to `dimos_module::NoConfig`. - Unattributed fields are initialized via `Default::default()` and treated as module state. ## Config validation -`run()` calls `config.validate()` after deserializing and bails with an `io::Error` on failure. The attributes you'll usually reach for are `range` for numbers, `length` or `regex` for strings, and `custom(function = ...)` for one-off rules. Use `#[validate(schema(function = "..."))]` on the struct for cross-field invariants. +`run()` calls `config.validate()` after deserializing and bails with an `io::Error` on failure. Use `#[validate(schema(function = "..."))]` on the struct for cross-field invariants. ```rust use validator::{Validate, ValidationError}; diff --git a/native/rust/dimos-module/src/module.rs b/native/rust/dimos-module/src/module.rs index 32040497f4..d4d42e931b 100644 --- a/native/rust/dimos-module/src/module.rs +++ b/native/rust/dimos-module/src/module.rs @@ -21,7 +21,7 @@ pub trait ModuleConfig: DeserializeOwned + Debug + Validate {} impl ModuleConfig for T {} /// Default config type used by `#[derive(Module)]` when no `#[config]` field -/// is present. Just a stand in for modules that don't use configurations. +/// is used. Just a stand in for modules that don't use configurations. #[derive(Debug, Default, Deserialize)] #[serde(deny_unknown_fields)] pub struct NoConfig; From 0aa0e8ff01094d90f7f3e2bf0f3cb91ba4b3cfa4 Mon Sep 17 00:00:00 2001 From: Andrew Lauer Date: Mon, 1 Jun 2026 18:01:26 -0700 Subject: [PATCH 5/5] Fix --- native/rust/dimos-module/src/module.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/native/rust/dimos-module/src/module.rs b/native/rust/dimos-module/src/module.rs index d4d42e931b..f73ed1522a 100644 --- a/native/rust/dimos-module/src/module.rs +++ b/native/rust/dimos-module/src/module.rs @@ -144,6 +144,14 @@ fn parse_config_json(line: &str) -> io::Result<(HashMap String { + if field == "__all__" { + message + } else { + format!("{field}: {message}") + } +} + fn format_validation_errors(errors: &validator::ValidationErrors) -> String { use validator::ValidationErrorsKind; let mut messages = Vec::new(); @@ -151,6 +159,7 @@ fn format_validation_errors(errors: &validator::ValidationErrors) -> String { match kind { ValidationErrorsKind::Field(field_errs) => { for err in field_errs { + let label = err.message.as_deref().unwrap_or(err.code.as_ref()); let mut bounds: Vec = err .params .iter() @@ -168,11 +177,11 @@ fn format_validation_errors(errors: &validator::ValidationErrors) -> String { .get("value") .map(|v| format!(" got {v}")) .unwrap_or_default(); - messages.push(format!("{field}: {}{bounds_str}{got}", err.code)); + messages.push(with_field(field, format!("{label}{bounds_str}{got}"))); } } ValidationErrorsKind::Struct(nested) => { - messages.push(format!("{field}: {}", format_validation_errors(nested))); + messages.push(with_field(field, format_validation_errors(nested))); } ValidationErrorsKind::List(list) => { for (idx, errs) in list {