Skip to main content

shape_runtime/stdlib/
env.rs

1//! Native `env` module for environment variable and system info access.
2//!
3//! Phase 2b canary: migrated to the typed marshal layer
4//! (`crate::marshal::register_typed_fn_N`). Native function bodies take
5//! typed Rust args via [`shape_runtime::marshal::FromSlot`]; their Rust
6//! signatures *are* the typed signatures. The Rust trait system rejects
7//! registration whose body's parameter types don't match.
8//!
9//! Currently exports the all-scalar-return subset:
10//!   `env.has`, `env.cwd`, `env.os`, `env.arch`.
11//!
12//! Pending Phase 2c marshal extensions:
13//!   - `env.get`  (`Option<string>` return — needs `ToSlot` for Option/None)
14//!   - `env.all`  (`HashMap<string, string>` return — needs Map marshal)
15//!   - `env.args` (`Array<string>` return — needs Array marshal)
16//!
17//! Policy gated: requires `Env` permission at runtime.
18//!
19//! See `docs/defections.md` 2026-05-06 (Phase 2b unified marshal).
20
21use crate::marshal::{register_typed_fn_0, register_typed_fn_1};
22use crate::module_exports::ModuleExports;
23use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
24use std::sync::Arc;
25
26/// Create the `env` module with environment variable and system info functions.
27pub fn create_env_module() -> ModuleExports {
28    let mut module = ModuleExports::new("std::core::env");
29    module.description = "Environment variables and system information".to_string();
30
31    // env.has(name: string) -> bool
32    register_typed_fn_1::<_, Arc<String>>(
33        &mut module,
34        "has",
35        "Check if an environment variable is set",
36        "name",
37        "string",
38        ConcreteType::Bool,
39        |name, ctx| {
40            crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::Env)?;
41            Ok(TypedReturn::Concrete(ConcreteReturn::Bool(
42                std::env::var(name.as_str()).is_ok(),
43            )))
44        },
45    );
46
47    // env.cwd() -> string
48    register_typed_fn_0(
49        &mut module,
50        "cwd",
51        "Get the current working directory",
52        ConcreteType::String,
53        |ctx| {
54            crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::Env)?;
55            let cwd = std::env::current_dir().map_err(|e| format!("env.cwd() failed: {}", e))?;
56            Ok(TypedReturn::Concrete(ConcreteReturn::String(
57                cwd.to_string_lossy().into_owned(),
58            )))
59        },
60    );
61
62    // env.os() -> string
63    register_typed_fn_0(
64        &mut module,
65        "os",
66        "Get the operating system name (e.g. linux, macos, windows)",
67        ConcreteType::String,
68        |ctx| {
69            crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::Env)?;
70            Ok(TypedReturn::Concrete(ConcreteReturn::String(
71                std::env::consts::OS.to_string(),
72            )))
73        },
74    );
75
76    // env.arch() -> string
77    register_typed_fn_0(
78        &mut module,
79        "arch",
80        "Get the CPU architecture (e.g. x86_64, aarch64)",
81        ConcreteType::String,
82        |ctx| {
83            crate::module_exports::check_permission(ctx, shape_abi_v1::Permission::Env)?;
84            Ok(TypedReturn::Concrete(ConcreteReturn::String(
85                std::env::consts::ARCH.to_string(),
86            )))
87        },
88    );
89
90    module
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_env_module_creation() {
99        let module = create_env_module();
100        assert_eq!(module.name, "std::core::env");
101        assert!(module.has_export("has"));
102        assert!(module.has_export("cwd"));
103        assert!(module.has_export("os"));
104        assert!(module.has_export("arch"));
105    }
106
107    #[test]
108    fn test_env_typed_registry_arg_kinds() {
109        let module = create_env_module();
110        let typed = module.typed_exports();
111
112        // Phase 2b structural property: arg_kinds is derived from the body's
113        // Rust parameter types via FromSlot::NATIVE_KIND. env.has takes
114        // one Arc<String>, which is NativeKind::String.
115        let has_entry = typed.get("has").unwrap();
116        assert_eq!(has_entry.arg_kinds.len(), 1);
117        assert_eq!(has_entry.arg_kinds[0], shape_value::NativeKind::String);
118        assert_eq!(has_entry.return_type, ConcreteType::Bool);
119
120        // Zero-arg functions populate empty arg_kinds.
121        let cwd_entry = typed.get("cwd").unwrap();
122        assert!(cwd_entry.arg_kinds.is_empty());
123        assert_eq!(cwd_entry.return_type, ConcreteType::String);
124    }
125
126    #[test]
127    fn test_env_schemas() {
128        let module = create_env_module();
129        let has_schema = module.get_schema("has").unwrap();
130        assert_eq!(has_schema.params.len(), 1);
131        assert_eq!(has_schema.return_type.as_deref(), Some("bool"));
132
133        let os_schema = module.get_schema("os").unwrap();
134        assert_eq!(os_schema.return_type.as_deref(), Some("string"));
135    }
136}