feat(phase109): RuntimeProfile設計で FileBox を条件付き optional に
Phase 109 完全実装完了: - RuntimeProfile enum (Default, NoFs) で profile 制御 - CoreBoxId.is_required_in(profile) で条件付き required/optional - initialize_runtime() で env 読み込み責務を一元化(修正1) - NoFsFileIo スタブで no-fs プロファイルでの FileBox 無効化(修正2) - Logger/ConsoleService は no-fs でも有効と明示(修正2) - Profile 拡張予定(TestMock/Sandbox/ReadOnly/Embedded)を予約(修正3) 実装ファイル: - src/runtime/runtime_profile.rs (新規) - src/providers/ring1/file/nofs_fileio.rs (新規) - src/runtime/core_box_ids.rs (修正) - src/runtime/plugin_host.rs (修正) - src/runtime/provider_lock.rs (修正) - docs 更新 テスト: Phase 109 11/11 PASS ✅ ビルド: cargo build --release SUCCESS ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -3,6 +3,8 @@
|
||||
//! Nyash の core Box を型安全な enum で管理する。
|
||||
//! ハードコード文字列からの脱却により、コンパイル時検証を実現。
|
||||
|
||||
use crate::runtime::runtime_profile::RuntimeProfile;
|
||||
|
||||
/// Phase 85 調査結果に基づく Core Box ID
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum CoreBoxId {
|
||||
@ -118,6 +120,30 @@ impl CoreBoxId {
|
||||
matches!(self, String | Integer | Bool | Array | Map | Console | File)
|
||||
}
|
||||
|
||||
/// Phase 109: profile-aware required check
|
||||
///
|
||||
/// Determines if this CoreBox is required in the given RuntimeProfile.
|
||||
///
|
||||
/// - Default: Same as is_core_required() (FileBox is required)
|
||||
/// - NoFs: FileBox becomes optional (only String/Integer/Bool/Array/Map/Console required)
|
||||
///
|
||||
/// **Future expansion**: TestMock/Sandbox/ReadOnly/Embedded profiles will extend this logic
|
||||
pub fn is_required_in(&self, profile: &RuntimeProfile) -> bool {
|
||||
use CoreBoxId::*;
|
||||
let core_required = matches!(self, String | Integer | Bool | Array | Map | Console);
|
||||
|
||||
match profile {
|
||||
RuntimeProfile::Default => {
|
||||
// Phase 106: FileBox is required in Default profile
|
||||
self.is_core_required()
|
||||
}
|
||||
RuntimeProfile::NoFs => {
|
||||
// Phase 109: FileBox is optional in NoFs profile
|
||||
core_required
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 87: カテゴリ分類
|
||||
pub fn category(&self) -> CoreBoxCategory {
|
||||
use CoreBoxId::*;
|
||||
@ -372,6 +398,32 @@ mod tests {
|
||||
assert_eq!(CoreBoxId::Function.category(), CoreBoxCategory::Special);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_core_box_id_is_required_in_default() {
|
||||
use crate::runtime::runtime_profile::RuntimeProfile;
|
||||
let profile = RuntimeProfile::Default;
|
||||
assert!(CoreBoxId::String.is_required_in(&profile));
|
||||
assert!(CoreBoxId::Integer.is_required_in(&profile));
|
||||
assert!(CoreBoxId::Bool.is_required_in(&profile));
|
||||
assert!(CoreBoxId::Array.is_required_in(&profile));
|
||||
assert!(CoreBoxId::Map.is_required_in(&profile));
|
||||
assert!(CoreBoxId::Console.is_required_in(&profile));
|
||||
assert!(CoreBoxId::File.is_required_in(&profile)); // FileBox required in Default
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_core_box_id_is_required_in_nofs() {
|
||||
use crate::runtime::runtime_profile::RuntimeProfile;
|
||||
let profile = RuntimeProfile::NoFs;
|
||||
assert!(CoreBoxId::String.is_required_in(&profile));
|
||||
assert!(CoreBoxId::Integer.is_required_in(&profile));
|
||||
assert!(CoreBoxId::Bool.is_required_in(&profile));
|
||||
assert!(CoreBoxId::Array.is_required_in(&profile));
|
||||
assert!(CoreBoxId::Map.is_required_in(&profile));
|
||||
assert!(CoreBoxId::Console.is_required_in(&profile));
|
||||
assert!(!CoreBoxId::File.is_required_in(&profile)); // FileBox optional in NoFs
|
||||
}
|
||||
|
||||
// ===== CoreMethodId tests =====
|
||||
|
||||
#[test]
|
||||
|
||||
@ -9,6 +9,7 @@ pub mod deprecations;
|
||||
pub mod gc;
|
||||
pub mod plugin_host; // Phase 91: PluginHost skeleton
|
||||
pub mod ring0; // Phase 88: Ring0Context - OS API 抽象化レイヤー
|
||||
pub mod runtime_profile; // Phase 109: RuntimeProfile enum (Default/NoFs)
|
||||
pub mod gc_controller;
|
||||
pub mod gc_mode;
|
||||
pub mod gc_trace;
|
||||
@ -42,6 +43,7 @@ pub use box_registry::{get_global_registry, BoxFactoryRegistry, BoxProvider};
|
||||
pub use core_box_ids::{CoreBoxCategory, CoreBoxId, CoreMethodId}; // Phase 87: 型安全enum
|
||||
pub use plugin_config::PluginConfig;
|
||||
pub use ring0::{get_global_ring0, init_global_ring0, Ring0Context}; // Phase 88: Ring0 公開 API
|
||||
pub use runtime_profile::RuntimeProfile; // Phase 109: RuntimeProfile enum
|
||||
pub use plugin_host::CoreInitError; // Phase 92: CoreServices 初期化エラー
|
||||
pub use plugin_loader_unified::{
|
||||
get_global_plugin_host, init_global_plugin_host, MethodHandle, PluginBoxType, PluginHost,
|
||||
@ -98,21 +100,55 @@ macro_rules! console_println {
|
||||
};
|
||||
}
|
||||
|
||||
/// Runtime 初期化(Phase 95: global accessor 実装完了)
|
||||
/// Runtime 初期化(Phase 95/109: profile-aware initialization)
|
||||
///
|
||||
/// Phase 94: フォールバック削除 - 常に実際の Box を使用
|
||||
/// Phase 95: global に登録して get_core_plugin_host() でアクセス可能に
|
||||
/// Phase 109: RuntimeProfile に基づく条件付き初期化
|
||||
///
|
||||
/// # Responsibility Separation (Phase 109 Modification 1)
|
||||
///
|
||||
/// - **initialize_runtime**: 環境変数から profile を読む(唯一の env reader)
|
||||
/// - **PluginHost**: profile を引数として受け取る(env に依存しない)
|
||||
///
|
||||
/// # Profile behavior
|
||||
///
|
||||
/// - **Default**: FileBox provider 必須(Fail-Fast)、全 core services 有効
|
||||
/// - **NoFs**: FileBox provider optional(disabled stub)、core services のみ有効
|
||||
pub fn initialize_runtime(ring0: std::sync::Arc<Ring0Context>) -> Result<(), CoreInitError> {
|
||||
use crate::box_factory::UnifiedBoxRegistry;
|
||||
use crate::box_factory::builtin::BuiltinBoxFactory;
|
||||
|
||||
// Phase 109: Read RuntimeProfile from environment (this layer only)
|
||||
let profile = RuntimeProfile::from_env();
|
||||
|
||||
let mut registry = UnifiedBoxRegistry::with_env_policy();
|
||||
|
||||
// Phase 94: BuiltinBoxFactory を登録して core_required Boxes を提供
|
||||
registry.register(std::sync::Arc::new(BuiltinBoxFactory::new()));
|
||||
|
||||
// Phase 94: 常に実際の Box → Service 変換を使用(Fail-Fast原則)
|
||||
let plugin_host = plugin_host::PluginHost::with_core_from_registry(ring0, ®istry)?;
|
||||
// Phase 109: Profile-aware FileBox provider initialization
|
||||
// Note: This is done BEFORE PluginHost initialization to allow plugin override
|
||||
match profile {
|
||||
RuntimeProfile::Default => {
|
||||
// Default profile: FileBox provider will be auto-registered in PluginHost
|
||||
// (no action needed here, kept for documentation)
|
||||
}
|
||||
RuntimeProfile::NoFs => {
|
||||
// NoFs profile: Register NoFsFileIo stub
|
||||
use crate::runtime::provider_lock;
|
||||
let _ = provider_lock::init_filebox_provider_for_profile(&ring0, &profile);
|
||||
// Ignore error - PluginHost will handle missing provider gracefully
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 109: Pass profile to PluginHost (env-independent)
|
||||
let plugin_host = plugin_host::PluginHost::with_core_from_registry_optional(
|
||||
ring0,
|
||||
®istry,
|
||||
plugin_host::CoreServicesConfig::all_enabled(),
|
||||
&profile,
|
||||
)?;
|
||||
plugin_host.ensure_core_initialized();
|
||||
|
||||
// Phase 95: global に登録
|
||||
|
||||
@ -8,6 +8,7 @@ use std::collections::HashMap;
|
||||
use std::any::Any;
|
||||
use crate::box_factory::UnifiedBoxRegistry;
|
||||
use crate::runtime::CoreBoxId;
|
||||
use crate::runtime::RuntimeProfile;
|
||||
|
||||
/// Phase 103: CoreServices Optional化設定
|
||||
///
|
||||
@ -98,7 +99,8 @@ use super::ring0::Ring0Context;
|
||||
pub enum CoreInitError {
|
||||
MissingService {
|
||||
box_id: CoreBoxId,
|
||||
message: String,
|
||||
/// Phase 109: hint now includes profile context
|
||||
hint: String,
|
||||
},
|
||||
RegistryEmpty,
|
||||
InvalidServiceType {
|
||||
@ -111,8 +113,8 @@ pub enum CoreInitError {
|
||||
impl std::fmt::Display for CoreInitError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
CoreInitError::MissingService { box_id, message } => {
|
||||
write!(f, "Missing core service {:?}: {}", box_id, message)
|
||||
CoreInitError::MissingService { box_id, hint } => {
|
||||
write!(f, "Missing core service {:?}: {}", box_id, hint)
|
||||
}
|
||||
CoreInitError::RegistryEmpty => {
|
||||
write!(f, "UnifiedBoxRegistry is empty")
|
||||
@ -143,43 +145,73 @@ impl PluginHost {
|
||||
unimplemented!("Phase 92 で from_registry() 実装後に削除")
|
||||
}
|
||||
|
||||
/// Phase 103: Optional CoreServices initialization
|
||||
/// Phase 103/109: Optional CoreServices initialization with RuntimeProfile support
|
||||
///
|
||||
/// Allows selective initialization based on CoreServicesConfig.
|
||||
/// Allows selective initialization based on CoreServicesConfig and RuntimeProfile.
|
||||
/// ConsoleBox is mandatory for user-facing output.
|
||||
///
|
||||
/// Phase 109 additions:
|
||||
/// - `profile` parameter controls FileBox provider requirements
|
||||
/// - Default profile: FileBox provider is required (Fail-Fast)
|
||||
/// - NoFs profile: FileBox provider is optional (disabled)
|
||||
pub fn with_core_from_registry_optional(
|
||||
ring0: Arc<Ring0Context>,
|
||||
registry: &UnifiedBoxRegistry,
|
||||
config: CoreServicesConfig,
|
||||
profile: &RuntimeProfile,
|
||||
) -> Result<Self, CoreInitError> {
|
||||
use crate::runtime::core_services::*;
|
||||
use crate::runtime::provider_lock;
|
||||
|
||||
// Phase 107: Auto-register default FileBox provider (Ring0.FsApi-based)
|
||||
// This happens before the Phase 106 check, so plugins can still override.
|
||||
// If a plugin has already registered, this returns Err but we continue.
|
||||
if CoreBoxId::File.is_core_required() {
|
||||
match provider_lock::init_default_filebox_provider(&ring0) {
|
||||
Ok(()) => {
|
||||
// Default provider registered successfully
|
||||
ring0.log.debug("[Phase 107] Ring0FsFileIo registered as default FileBox provider");
|
||||
}
|
||||
Err(msg) => {
|
||||
// Plugin provider already registered - this is OK (plugin priority)
|
||||
ring0.log.debug(&format!("[Phase 107] {}", msg));
|
||||
}
|
||||
// Phase 109: Profile-aware required check
|
||||
for box_id in CoreBoxId::iter() {
|
||||
if box_id.is_required_in(profile) && !registry.has_type(box_id.name()) {
|
||||
return Err(CoreInitError::MissingService {
|
||||
box_id,
|
||||
hint: format!(
|
||||
"Core Box {} is required in {} profile but not found in registry",
|
||||
box_id.name(),
|
||||
profile.name()
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 106: FileBox provider チェック追加
|
||||
// CoreBoxId がFileを必須と判定している場合、provider が登録されていることを確認
|
||||
// Phase 107: With auto-registration above, this check should always pass
|
||||
if CoreBoxId::File.is_core_required() {
|
||||
if provider_lock::get_filebox_provider().is_none() {
|
||||
return Err(CoreInitError::MissingService {
|
||||
box_id: CoreBoxId::File,
|
||||
message: "FileBox provider not registered (required for selfhost/default profile)".to_string(),
|
||||
});
|
||||
// Phase 109: Profile-aware FileBox provider check
|
||||
match profile {
|
||||
RuntimeProfile::Default => {
|
||||
// Phase 107: Auto-register default FileBox provider (Ring0.FsApi-based)
|
||||
// This happens before the Phase 106 check, so plugins can still override.
|
||||
// If a plugin has already registered, this returns Err but we continue.
|
||||
if CoreBoxId::File.is_required_in(profile) {
|
||||
match provider_lock::init_default_filebox_provider(&ring0) {
|
||||
Ok(()) => {
|
||||
// Default provider registered successfully
|
||||
ring0.log.debug("[Phase 107] Ring0FsFileIo registered as default FileBox provider");
|
||||
}
|
||||
Err(msg) => {
|
||||
// Plugin provider already registered - this is OK (plugin priority)
|
||||
ring0.log.debug(&format!("[Phase 107] {}", msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 106/109: FileBox provider チェック (Default profile)
|
||||
// CoreBoxId がFileを必須と判定している場合、provider が登録されていることを確認
|
||||
// Phase 107: With auto-registration above, this check should always pass
|
||||
if CoreBoxId::File.is_required_in(profile) {
|
||||
if provider_lock::get_filebox_provider().is_none() {
|
||||
return Err(CoreInitError::MissingService {
|
||||
box_id: CoreBoxId::File,
|
||||
hint: "FileBox provider not registered (required for Default profile)".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
RuntimeProfile::NoFs => {
|
||||
// Phase 109: FileBox provider is optional in NoFs profile
|
||||
// We skip provider initialization and checks entirely
|
||||
ring0.log.debug("[Phase 109] NoFs profile: FileBox provider skipped");
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +229,7 @@ impl PluginHost {
|
||||
if !registry.has_type("StringBox") {
|
||||
return Err(CoreInitError::MissingService {
|
||||
box_id: CoreBoxId::String,
|
||||
message: "StringBox enabled but not found in registry".to_string(),
|
||||
hint: "StringBox enabled but not found in registry".to_string(),
|
||||
});
|
||||
}
|
||||
core.string = Some(Arc::new(StringBoxAdapter::new()));
|
||||
@ -208,7 +240,7 @@ impl PluginHost {
|
||||
if !registry.has_type("IntegerBox") {
|
||||
return Err(CoreInitError::MissingService {
|
||||
box_id: CoreBoxId::Integer,
|
||||
message: "IntegerBox enabled but not found in registry".to_string(),
|
||||
hint: "IntegerBox enabled but not found in registry".to_string(),
|
||||
});
|
||||
}
|
||||
core.integer = Some(Arc::new(IntegerBoxAdapter::new()));
|
||||
@ -219,7 +251,7 @@ impl PluginHost {
|
||||
if !registry.has_type("BoolBox") {
|
||||
return Err(CoreInitError::MissingService {
|
||||
box_id: CoreBoxId::Bool,
|
||||
message: "BoolBox enabled but not found in registry".to_string(),
|
||||
hint: "BoolBox enabled but not found in registry".to_string(),
|
||||
});
|
||||
}
|
||||
core.bool = Some(Arc::new(BoolBoxAdapter::new()));
|
||||
@ -230,7 +262,7 @@ impl PluginHost {
|
||||
if !registry.has_type("ArrayBox") {
|
||||
return Err(CoreInitError::MissingService {
|
||||
box_id: CoreBoxId::Array,
|
||||
message: "ArrayBox enabled but not found in registry".to_string(),
|
||||
hint: "ArrayBox enabled but not found in registry".to_string(),
|
||||
});
|
||||
}
|
||||
core.array = Some(Arc::new(ArrayBoxAdapter::new()));
|
||||
@ -241,7 +273,7 @@ impl PluginHost {
|
||||
if !registry.has_type("MapBox") {
|
||||
return Err(CoreInitError::MissingService {
|
||||
box_id: CoreBoxId::Map,
|
||||
message: "MapBox enabled but not found in registry".to_string(),
|
||||
hint: "MapBox enabled but not found in registry".to_string(),
|
||||
});
|
||||
}
|
||||
core.map = Some(Arc::new(MapBoxAdapter::new()));
|
||||
@ -252,14 +284,14 @@ impl PluginHost {
|
||||
if !registry.has_type("ConsoleBox") {
|
||||
return Err(CoreInitError::MissingService {
|
||||
box_id: CoreBoxId::Console,
|
||||
message: "ConsoleBox is mandatory but not found in registry".to_string(),
|
||||
hint: "ConsoleBox is mandatory but not found in registry".to_string(),
|
||||
});
|
||||
}
|
||||
core.console = Some(Arc::new(ConsoleBoxAdapter::new()));
|
||||
} else {
|
||||
return Err(CoreInitError::MissingService {
|
||||
box_id: CoreBoxId::Console,
|
||||
message: "Phase 103: ConsoleBox is mandatory for user-facing output".to_string(),
|
||||
hint: "Phase 103: ConsoleBox is mandatory for user-facing output".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -270,19 +302,22 @@ impl PluginHost {
|
||||
})
|
||||
}
|
||||
|
||||
/// Phase 101: Backward compatibility - all services required
|
||||
/// Phase 101/109: Backward compatibility - all services required with Default profile
|
||||
///
|
||||
/// Maintains existing behavior: all CoreServices must be present.
|
||||
/// Used by default_ring0 and other initialization paths expecting all services.
|
||||
///
|
||||
/// Phase 109: Now uses Default profile (FileBox required)
|
||||
pub fn with_core_from_registry(
|
||||
ring0: Arc<Ring0Context>,
|
||||
registry: &UnifiedBoxRegistry,
|
||||
) -> Result<Self, CoreInitError> {
|
||||
// Use all_enabled() for backward compatibility
|
||||
// Use all_enabled() and Default profile for backward compatibility
|
||||
Self::with_core_from_registry_optional(
|
||||
ring0,
|
||||
registry,
|
||||
CoreServicesConfig::all_enabled(),
|
||||
&RuntimeProfile::Default,
|
||||
)
|
||||
}
|
||||
|
||||
@ -389,7 +424,7 @@ mod tests {
|
||||
fn test_core_init_error_display() {
|
||||
let error = CoreInitError::MissingService {
|
||||
box_id: CoreBoxId::String,
|
||||
message: "StringBox not found".to_string(),
|
||||
hint: "StringBox not found".to_string(),
|
||||
};
|
||||
let display = format!("{}", error);
|
||||
assert!(display.contains("String"));
|
||||
@ -450,6 +485,29 @@ mod tests {
|
||||
panic!("FileBox provider should be registered after with_core_from_registry");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_core_from_registry_nofs_filebox_optional() {
|
||||
// Phase 109: NoFs profile では FileBox provider なしで OK
|
||||
use crate::runtime::ring0::default_ring0;
|
||||
use crate::box_factory::builtin::BuiltinBoxFactory;
|
||||
|
||||
let ring0 = Arc::new(default_ring0());
|
||||
let mut registry = UnifiedBoxRegistry::new();
|
||||
registry.register(Arc::new(BuiltinBoxFactory::new()));
|
||||
|
||||
// Phase 109: NoFs profile で初期化
|
||||
let profile = RuntimeProfile::NoFs;
|
||||
let result = PluginHost::with_core_from_registry_optional(
|
||||
ring0,
|
||||
®istry,
|
||||
CoreServicesConfig::all_enabled(),
|
||||
&profile,
|
||||
);
|
||||
|
||||
// Phase 109: FileBox は optional なので、provider なしで成功するはず
|
||||
assert!(result.is_ok(), "Expected success with NoFs profile (FileBox optional)");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -92,3 +92,40 @@ pub fn init_default_filebox_provider(
|
||||
set_filebox_provider(provider)
|
||||
.map_err(|_| "Plugin FileBox provider already registered".to_string())
|
||||
}
|
||||
|
||||
/// Phase 109: Initialize FileBox provider based on RuntimeProfile
|
||||
///
|
||||
/// This helper registers the appropriate FileBox provider for the given profile:
|
||||
/// - Default: Ring0FsFileIo (Ring0.FsApi-based, read/write support)
|
||||
/// - NoFs: NoFsFileIo (stub that returns Unsupported errors)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Ok(())`: Provider registered successfully
|
||||
/// - `Err(msg)`: Provider already registered (plugin took precedence)
|
||||
///
|
||||
/// # Design (Phase 109)
|
||||
///
|
||||
/// This function is called during initialize_runtime() and respects the profile:
|
||||
/// - Default profile: Registers Ring0FsFileIo for full filesystem access
|
||||
/// - NoFs profile: Registers NoFsFileIo stub (all operations return Unsupported)
|
||||
pub fn init_filebox_provider_for_profile(
|
||||
ring0: &Arc<crate::runtime::ring0::Ring0Context>,
|
||||
profile: &crate::runtime::RuntimeProfile,
|
||||
) -> Result<(), String> {
|
||||
use crate::runtime::RuntimeProfile;
|
||||
|
||||
match profile {
|
||||
RuntimeProfile::Default => {
|
||||
// Phase 107: Standard profile uses Ring0FsFileIo
|
||||
init_default_filebox_provider(ring0)
|
||||
}
|
||||
RuntimeProfile::NoFs => {
|
||||
// Phase 109: NoFs profile uses NoFsFileIo stub
|
||||
use crate::providers::ring1::file::nofs_fileio::NoFsFileIo;
|
||||
let provider = Arc::new(NoFsFileIo);
|
||||
set_filebox_provider(provider)
|
||||
.map_err(|_| "FileBox provider already set (unexpected in NoFs profile)".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
src/runtime/runtime_profile.rs
Normal file
74
src/runtime/runtime_profile.rs
Normal file
@ -0,0 +1,74 @@
|
||||
//! Phase 109: RuntimeProfile enum
|
||||
//!
|
||||
//! Controls conditional core service initialization based on runtime profile.
|
||||
//! Supports Default (selfhost/standard) and NoFs (minimal runtime without filesystem).
|
||||
|
||||
/// Phase 109: RuntimeProfile
|
||||
///
|
||||
/// Controls availability of FileBox and other optional services.
|
||||
///
|
||||
/// - Default: selfhost/standard - most services enabled (FileBox required)
|
||||
/// - NoFs: minimal runtime - FileBox disabled, core boxes only
|
||||
///
|
||||
/// Future expansion planned: TestMock, Sandbox, ReadOnly, Embedded
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RuntimeProfile {
|
||||
/// Standard runtime (selfhost/default)
|
||||
Default,
|
||||
/// Minimal runtime without FileSystem
|
||||
NoFs,
|
||||
}
|
||||
|
||||
impl RuntimeProfile {
|
||||
/// Read RuntimeProfile from NYASH_RUNTIME_PROFILE environment variable
|
||||
///
|
||||
/// # Recognized values
|
||||
///
|
||||
/// - `"no-fs"` or `"nofs"` → NoFs
|
||||
/// - Any other value or missing → Default
|
||||
pub fn from_env() -> Self {
|
||||
match std::env::var("NYASH_RUNTIME_PROFILE").as_deref() {
|
||||
Ok("no-fs") | Ok("nofs") => RuntimeProfile::NoFs,
|
||||
_ => RuntimeProfile::Default,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get profile name for debugging
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
RuntimeProfile::Default => "Default",
|
||||
RuntimeProfile::NoFs => "NoFs",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_runtime_profile_from_env_default() {
|
||||
// Without setting env, should return Default
|
||||
std::env::remove_var("NYASH_RUNTIME_PROFILE");
|
||||
assert_eq!(RuntimeProfile::from_env(), RuntimeProfile::Default);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runtime_profile_from_env_nofs() {
|
||||
// Test "no-fs" variant
|
||||
std::env::set_var("NYASH_RUNTIME_PROFILE", "no-fs");
|
||||
assert_eq!(RuntimeProfile::from_env(), RuntimeProfile::NoFs);
|
||||
|
||||
// Test "nofs" variant
|
||||
std::env::set_var("NYASH_RUNTIME_PROFILE", "nofs");
|
||||
assert_eq!(RuntimeProfile::from_env(), RuntimeProfile::NoFs);
|
||||
|
||||
std::env::remove_var("NYASH_RUNTIME_PROFILE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runtime_profile_name() {
|
||||
assert_eq!(RuntimeProfile::Default.name(), "Default");
|
||||
assert_eq!(RuntimeProfile::NoFs.name(), "NoFs");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user