Skip to content

feat: native module config macro#2503

Merged
leshy merged 6 commits into
mainfrom
andrew/feat/enforce-all-rust-config-fields
Jun 17, 2026
Merged

feat: native module config macro#2503
leshy merged 6 commits into
mainfrom
andrew/feat/enforce-all-rust-config-fields

Conversation

@aclauer

@aclauer aclauer commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

Config defaults for native modules should only be declared in one place (the Python wrapper) and the native module config should have fields iff they are supplied by Python.

Closes DIM-XXX

Solution

Introduce a native_config proc macro for Rust native module config structs. It reduces the boiler plate and ensure that there are no optional configs or default values.

How to Test

Contributor License Agreement

  • I have read and approved the CLA.

@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

@@            Coverage Diff             @@
##             main    #2503      +/-   ##
==========================================
- Coverage   70.09%   69.97%   -0.13%     
==========================================
  Files         838      838              
  Lines       74337    74337              
  Branches     6667     6667              
==========================================
- Hits        52109    52018      -91     
- Misses      20553    20618      +65     
- Partials     1675     1701      +26     
Flag Coverage Δ
OS-ubuntu-24.04-arm 63.83% <ø> (ø)
OS-ubuntu-latest 64.67% <ø> (+<0.01%) ⬆️
Py-3.10 64.66% <ø> (ø)
Py-3.11 64.66% <ø> (ø)
Py-3.12 64.66% <ø> (+<0.01%) ⬆️
Py-3.13 64.66% <ø> (+<0.01%) ⬆️
Py-3.14 64.67% <ø> (+<0.01%) ⬆️
Py-3.14t 64.66% <ø> (+<0.01%) ⬆️
SelfHosted-Large 30.29% <ø> (-0.06%) ⬇️
SelfHosted-Linux 38.21% <ø> (+<0.01%) ⬆️
SelfHosted-macOS 36.94% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aclauer aclauer marked this pull request as ready for review June 16, 2026 17:14
@aclauer aclauer changed the title Andrew/feat/enforce all rust config fields feat: native module config macro Jun 16, 2026
@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a #[native_config] proc-macro attribute that consolidates config struct boilerplate and enforces a strict one-to-one mapping with the Python wrapper: every field must be supplied by Python with no Rust-side defaults.

  • Compile-time checks (check_native_config): rejects Option<T> fields, #[serde(default)], #[serde(skip)], #[serde(skip_deserializing)], and #[serde(flatten)]; injects #[derive(Debug, Deserialize, Serialize, Validate)] and #[serde(deny_unknown_fields)]; emits impl NativeConfig.
  • Runtime check (enforce_one_to_one): after deserialization, re-serializes the config and diffs the key sets to catch Option type-aliases that escape the compile-time guard; existing modules (voxel_ray_tracer, mls_planner, native_pong) are migrated to the new macro.

Confidence Score: 4/5

Safe to merge after addressing the missing skip_serializing guard in check_field_serde; all migrated callers use the macro correctly and the runtime key-set check is sound.

The macro's compile-time rejection list covers the main bypass cases (Option, serde(default), skip, skip_deserializing, flatten), but #[serde(skip_serializing)] and #[serde(skip_serializing_if)] are omitted. A field annotated with either will pass the compile-time check yet trigger an always-reject in enforce_one_to_one at runtime — the process can never start. The fix is a one-line addition to check_field_serde matching the existing skip/skip_deserializing branch.

native/rust/dimos-module-macros/src/lib.rs — check_field_serde needs to also forbid skip_serializing and skip_serializing_if.

Important Files Changed

Filename Overview
native/rust/dimos-module-macros/src/lib.rs New #[native_config] proc-macro with compile-time checks; forbids skip/skip_deserializing/flatten/Option/default but misses skip_serializing and skip_serializing_if, which would cause a runtime deadlock in enforce_one_to_one.
native/rust/dimos-module/src/module.rs Adds NativeConfig marker trait, Serialize bound, and enforce_one_to_one runtime key-set check. Logic is correct for Option fields and type-alias bypasses; BTreeSet diff and error messages are clear.
native/rust/dimos-module/src/lib.rs Re-exports native_config macro and NativeConfig trait; straightforward additive change.
dimos/mapping/ray_tracing/rust/src/voxel_ray_tracer.rs Migrates Config to #[native_config]; removes manual serde/validator derive boilerplate. Correct usage.
dimos/navigation/nav_3d/mls_planner/rust/src/mls_planner.rs Migrates Config to #[native_config]; removes manual serde/validator derive boilerplate. Correct usage.
examples/native-modules/rust/src/native_pong.rs Example updated to use #[native_config]; removes Default derive which is now intentionally forbidden by the new contract.
native/rust/README.md Documentation updated to describe #[native_config] semantics, forbidden attributes, and runtime key-set check. Accurate and complete.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["#[native_config] applied to struct"] --> B{check_native_config}
    B -->|tuple struct / enum| C["compile error: wrong type"]
    B -->|Option field| D["compile error: Option forbidden"]
    B -->|serde default/skip/flatten| E["compile error: attribute forbidden"]
    B -->|passes| F["inject derive Debug+Deserialize+Serialize+Validate\n+ serde(deny_unknown_fields)\n+ impl NativeConfig"]

    F --> G["Module::Config bound: ModuleConfig\n= DeserializeOwned+Serialize+Debug+Validate+NativeConfig"]
    G --> H["run() reads stdin JSON"]
    H --> I["parse_config_json: serde::from_value"]
    I -->|deser error| J["io::Error: failed to deserialize"]
    I -->|ok| K["enforce_one_to_one\ncompare provided_keys vs expected_keys"]
    K -->|missing or unexpected keys| L["io::Error: config keys do not match\n(catches Option type-aliases)"]
    K -->|keys match| M["validate_config: config.validate()"]
    M -->|validation error| N["io::Error: config validation failed"]
    M -->|ok| O["Module::build → run loop"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["#[native_config] applied to struct"] --> B{check_native_config}
    B -->|tuple struct / enum| C["compile error: wrong type"]
    B -->|Option field| D["compile error: Option forbidden"]
    B -->|serde default/skip/flatten| E["compile error: attribute forbidden"]
    B -->|passes| F["inject derive Debug+Deserialize+Serialize+Validate\n+ serde(deny_unknown_fields)\n+ impl NativeConfig"]

    F --> G["Module::Config bound: ModuleConfig\n= DeserializeOwned+Serialize+Debug+Validate+NativeConfig"]
    G --> H["run() reads stdin JSON"]
    H --> I["parse_config_json: serde::from_value"]
    I -->|deser error| J["io::Error: failed to deserialize"]
    I -->|ok| K["enforce_one_to_one\ncompare provided_keys vs expected_keys"]
    K -->|missing or unexpected keys| L["io::Error: config keys do not match\n(catches Option type-aliases)"]
    K -->|keys match| M["validate_config: config.validate()"]
    M -->|validation error| N["io::Error: config validation failed"]
    M -->|ok| O["Module::build → run loop"]
Loading

Reviews (3): Last reviewed commit: "Check" | Re-trigger Greptile

Comment thread native/rust/dimos-module/src/module.rs
Comment thread native/rust/dimos-module-macros/src/lib.rs
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jun 16, 2026
@github-actions github-actions Bot removed the ready-to-merge Required CI checks have passed on this PR label Jun 16, 2026
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jun 16, 2026
@leshy leshy merged commit 271848f into main Jun 17, 2026
26 of 27 checks passed
@leshy leshy deleted the andrew/feat/enforce-all-rust-config-fields branch June 17, 2026 04:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants