mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
feat(code-mode): allow disabling session store and load
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
/// Configuration for one durable code-mode session.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct CodeModeSessionConfig {
|
||||
/// Controls whether cells can persist JSON values for later cells.
|
||||
pub store_load: StoreLoadMode,
|
||||
}
|
||||
|
||||
/// Controls whether the `store` and `load` globals are available in a session.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub enum StoreLoadMode {
|
||||
/// Expose `store` and `load` and retain their JSON values between cells.
|
||||
#[default]
|
||||
Enabled,
|
||||
/// Do not expose `store` or `load`, and do not retain JSON values.
|
||||
Disabled,
|
||||
}
|
||||
@@ -4,11 +4,17 @@ use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::CodeModeSessionConfig;
|
||||
use crate::PUBLIC_TOOL_NAME;
|
||||
use crate::StoreLoadMode;
|
||||
|
||||
const MAX_JS_SAFE_INTEGER: u64 = (1_u64 << 53) - 1;
|
||||
const DEFERRED_NESTED_TOOLS_GUIDANCE: &str = r#"Some deferred nested tools may be omitted from this description. They are still available on the global `tools` object and listed in `ALL_TOOLS`.
|
||||
To find one, filter `ALL_TOOLS` by `name` and `description`."#;
|
||||
const STORE_LOAD_HELPERS_PLACEHOLDER: &str = "{store_load_helpers}";
|
||||
const STORE_LOAD_HELPERS_DESCRIPTION: &str = r#"- `store(key: string, value: any)`: stores a serializable value under a string key for later `exec` calls in the same session.
|
||||
- `load(key: string)`: returns the stored value for a string key, or `undefined` if it is missing.
|
||||
"#;
|
||||
const EXEC_DESCRIPTION_TEMPLATE: &str = r#"Run JavaScript code to orchestrate/compose tool calls
|
||||
- Evaluates the provided JavaScript code in a fresh V8 isolate as an async module.
|
||||
- All nested tools are available on the global `tools` object, for example `await tools.exec_command(...)`. Tool names are exposed as normalized JavaScript identifiers, for example `await tools.mcp__ologs__get_profile(...)`.
|
||||
@@ -26,9 +32,7 @@ const EXEC_DESCRIPTION_TEMPLATE: &str = r#"Run JavaScript code to orchestrate/co
|
||||
- `text(value: string | number | boolean | undefined | null)`: Appends a text item. Non-string values are stringified with `JSON.stringify(...)` when possible.
|
||||
- `image(imageUrlOrItem: string | { image_url: string; detail?: "auto" | "low" | "high" | "original" | null } | ImageContent, detail?: "auto" | "low" | "high" | "original" | null)`: Appends an image item. `image_url` can be an HTTPS URL or a base64-encoded `data:` URL. To forward an MCP tool image, pass an individual `ImageContent` block from `result.content`, for example `image(result.content[0])`. MCP image blocks may request detail with `_meta: { "codex/imageDetail": "original" }`. When provided, the second `detail` argument overrides any detail embedded in the first argument.
|
||||
- `generatedImage(result: { image_url: string; output_hint?: string })`: Appends an image-generation result and its optional output hint.
|
||||
- `store(key: string, value: any)`: stores a serializable value under a string key for later `exec` calls in the same session.
|
||||
- `load(key: string)`: returns the stored value for a string key, or `undefined` if it is missing.
|
||||
- `notify(value: string | number | boolean | undefined | null)`: immediately injects an extra `custom_tool_call_output` for the current `exec` call. Values are stringified like `text(...)`.
|
||||
{store_load_helpers}- `notify(value: string | number | boolean | undefined | null)`: immediately injects an extra `custom_tool_call_output` for the current `exec` call. Values are stringified like `text(...)`.
|
||||
- `setTimeout(callback: () => void, delayMs?: number)`: schedules a callback to run later and returns a timeout id. Pending timeouts do not keep `exec` alive by themselves; await an explicit promise if you need to wait for one.
|
||||
- `clearTimeout(timeoutId?: number)`: cancels a timeout created by `setTimeout`.
|
||||
- `ALL_TOOLS`: metadata for the enabled nested tools as `{ name, description }` entries.
|
||||
@@ -253,9 +257,31 @@ pub fn build_exec_tool_description(
|
||||
namespace_descriptions: &BTreeMap<String, ToolNamespaceDescription>,
|
||||
code_mode_only: bool,
|
||||
deferred_tools_available: bool,
|
||||
) -> String {
|
||||
build_exec_tool_description_with_config(
|
||||
enabled_tools,
|
||||
namespace_descriptions,
|
||||
code_mode_only,
|
||||
deferred_tools_available,
|
||||
CodeModeSessionConfig::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_exec_tool_description_with_config(
|
||||
enabled_tools: &[ToolDefinition],
|
||||
namespace_descriptions: &BTreeMap<String, ToolNamespaceDescription>,
|
||||
code_mode_only: bool,
|
||||
deferred_tools_available: bool,
|
||||
session_config: CodeModeSessionConfig,
|
||||
) -> String {
|
||||
let mut sections = Vec::new();
|
||||
sections.push(EXEC_DESCRIPTION_TEMPLATE.to_string());
|
||||
let store_load_helpers = match session_config.store_load {
|
||||
StoreLoadMode::Enabled => STORE_LOAD_HELPERS_DESCRIPTION,
|
||||
StoreLoadMode::Disabled => "",
|
||||
};
|
||||
sections.push(
|
||||
EXEC_DESCRIPTION_TEMPLATE.replace(STORE_LOAD_HELPERS_PLACEHOLDER, store_load_helpers),
|
||||
);
|
||||
if deferred_tools_available {
|
||||
sections.push(DEFERRED_NESTED_TOOLS_GUIDANCE.to_string());
|
||||
}
|
||||
@@ -712,8 +738,11 @@ mod tests {
|
||||
use super::ToolNamespaceDescription;
|
||||
use super::augment_tool_definition;
|
||||
use super::build_exec_tool_description;
|
||||
use super::build_exec_tool_description_with_config;
|
||||
use super::normalize_code_mode_identifier;
|
||||
use super::parse_exec_source;
|
||||
use crate::CodeModeSessionConfig;
|
||||
use crate::StoreLoadMode;
|
||||
use codex_protocol::ToolName;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value as JsonValue;
|
||||
@@ -886,6 +915,22 @@ bar"
|
||||
assert!(description.contains("`clearTimeout(timeoutId?: number)`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_description_omits_disabled_store_load_helpers() {
|
||||
let description = build_exec_tool_description_with_config(
|
||||
&[],
|
||||
&BTreeMap::new(),
|
||||
/*code_mode_only*/ false,
|
||||
/*deferred_tools_available*/ false,
|
||||
CodeModeSessionConfig {
|
||||
store_load: StoreLoadMode::Disabled,
|
||||
},
|
||||
);
|
||||
|
||||
assert!(!description.contains("`store(key: string, value: any)`"));
|
||||
assert!(!description.contains("`load(key: string)`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_mode_only_description_groups_namespace_instructions_once() {
|
||||
let namespace_descriptions = BTreeMap::from([(
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
mod config;
|
||||
mod description;
|
||||
mod response;
|
||||
mod runtime;
|
||||
mod service;
|
||||
mod stored_values;
|
||||
|
||||
pub use config::CodeModeSessionConfig;
|
||||
pub use config::StoreLoadMode;
|
||||
pub use description::CODE_MODE_PRAGMA_PREFIX;
|
||||
pub use description::CodeModeToolKind;
|
||||
pub use description::ToolDefinition;
|
||||
pub use description::ToolNamespaceDescription;
|
||||
pub use description::augment_tool_definition;
|
||||
pub use description::build_exec_tool_description;
|
||||
pub use description::build_exec_tool_description_with_config;
|
||||
pub use description::build_wait_tool_description;
|
||||
pub use description::is_code_mode_nested_tool;
|
||||
pub use description::normalize_code_mode_identifier;
|
||||
|
||||
@@ -25,8 +25,6 @@ pub(super) fn install_globals(scope: &mut v8::PinScope<'_, '_>) -> Result<(), St
|
||||
let text = helper_function(scope, "text", text_callback)?;
|
||||
let image = helper_function(scope, "image", image_callback)?;
|
||||
let generated_image = helper_function(scope, "generatedImage", generated_image_callback)?;
|
||||
let store = helper_function(scope, "store", store_callback)?;
|
||||
let load = helper_function(scope, "load", load_callback)?;
|
||||
let notify = helper_function(scope, "notify", notify_callback)?;
|
||||
let yield_control = helper_function(scope, "yield_control", yield_control_callback)?;
|
||||
let exit = helper_function(scope, "exit", exit_callback)?;
|
||||
@@ -38,8 +36,15 @@ pub(super) fn install_globals(scope: &mut v8::PinScope<'_, '_>) -> Result<(), St
|
||||
set_global(scope, global, "text", text.into())?;
|
||||
set_global(scope, global, "image", image.into())?;
|
||||
set_global(scope, global, "generatedImage", generated_image.into())?;
|
||||
set_global(scope, global, "store", store.into())?;
|
||||
set_global(scope, global, "load", load.into())?;
|
||||
if scope
|
||||
.get_slot::<RuntimeState>()
|
||||
.is_some_and(|state| state.stored_values.is_enabled())
|
||||
{
|
||||
let store = helper_function(scope, "store", store_callback)?;
|
||||
let load = helper_function(scope, "load", load_callback)?;
|
||||
set_global(scope, global, "store", store.into())?;
|
||||
set_global(scope, global, "load", load.into())?;
|
||||
}
|
||||
set_global(scope, global, "notify", notify.into())?;
|
||||
set_global(scope, global, "yield_control", yield_control.into())?;
|
||||
set_global(scope, global, "exit", exit.into())?;
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::description::ToolDefinition;
|
||||
use crate::description::enabled_tool_metadata;
|
||||
use crate::response::FunctionCallOutputContentItem;
|
||||
use crate::service::CellId;
|
||||
use crate::stored_values::StoredValues;
|
||||
|
||||
pub const DEFAULT_EXEC_YIELD_TIME_MS: u64 = 10_000;
|
||||
pub const DEFAULT_WAIT_YIELD_TIME_MS: u64 = 10_000;
|
||||
@@ -165,13 +166,13 @@ pub(crate) enum RuntimeEvent {
|
||||
text: String,
|
||||
},
|
||||
Result {
|
||||
stored_value_writes: HashMap<String, JsonValue>,
|
||||
stored_value_writes: StoredValues,
|
||||
error_text: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_runtime(
|
||||
stored_values: HashMap<String, JsonValue>,
|
||||
stored_values: StoredValues,
|
||||
request: ExecuteRequest,
|
||||
event_tx: mpsc::UnboundedSender<RuntimeEvent>,
|
||||
pending_mode: PendingRuntimeMode,
|
||||
@@ -224,15 +225,15 @@ struct RuntimeConfig {
|
||||
tool_call_id: String,
|
||||
enabled_tools: Vec<EnabledToolMetadata>,
|
||||
source: String,
|
||||
stored_values: HashMap<String, JsonValue>,
|
||||
stored_values: StoredValues,
|
||||
}
|
||||
|
||||
pub(super) struct RuntimeState {
|
||||
event_tx: mpsc::UnboundedSender<RuntimeEvent>,
|
||||
pending_tool_calls: HashMap<String, v8::Global<v8::PromiseResolver>>,
|
||||
pending_timeouts: HashMap<u64, timers::ScheduledTimeout>,
|
||||
stored_values: HashMap<String, JsonValue>,
|
||||
stored_value_writes: HashMap<String, JsonValue>,
|
||||
stored_values: StoredValues,
|
||||
stored_value_writes: StoredValues,
|
||||
enabled_tools: Vec<EnabledToolMetadata>,
|
||||
next_tool_call_id: u64,
|
||||
next_timeout_id: u64,
|
||||
@@ -244,7 +245,7 @@ pub(super) struct RuntimeState {
|
||||
pub(super) enum CompletionState {
|
||||
Pending,
|
||||
Completed {
|
||||
stored_value_writes: HashMap<String, JsonValue>,
|
||||
stored_value_writes: StoredValues,
|
||||
error_text: Option<String>,
|
||||
},
|
||||
}
|
||||
@@ -289,8 +290,8 @@ fn run_runtime(
|
||||
event_tx: event_tx.clone(),
|
||||
pending_tool_calls: HashMap::new(),
|
||||
pending_timeouts: HashMap::new(),
|
||||
stored_value_writes: config.stored_values.empty_like(),
|
||||
stored_values: config.stored_values,
|
||||
stored_value_writes: HashMap::new(),
|
||||
enabled_tools: config.enabled_tools,
|
||||
next_tool_call_id: 1,
|
||||
next_timeout_id: 1,
|
||||
@@ -300,7 +301,7 @@ fn run_runtime(
|
||||
});
|
||||
|
||||
if let Err(error_text) = globals::install_globals(scope) {
|
||||
send_result(&event_tx, HashMap::new(), Some(error_text));
|
||||
capture_scope_send_error(scope, &event_tx, Some(error_text));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -418,7 +419,7 @@ fn capture_scope_send_error(
|
||||
|
||||
fn send_result(
|
||||
event_tx: &mpsc::UnboundedSender<RuntimeEvent>,
|
||||
stored_value_writes: HashMap<String, JsonValue>,
|
||||
stored_value_writes: StoredValues,
|
||||
error_text: Option<String>,
|
||||
) {
|
||||
let _ = event_tx.send(RuntimeEvent::Result {
|
||||
@@ -429,7 +430,6 @@ fn send_result(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -442,6 +442,8 @@ mod tests {
|
||||
use super::RuntimeEvent;
|
||||
use super::spawn_runtime;
|
||||
use crate::FunctionCallOutputContentItem;
|
||||
use crate::StoreLoadMode;
|
||||
use crate::stored_values::StoredValues;
|
||||
|
||||
fn execute_request(source: &str) -> ExecuteRequest {
|
||||
ExecuteRequest {
|
||||
@@ -457,7 +459,7 @@ mod tests {
|
||||
async fn terminate_execution_stops_cpu_bound_module() {
|
||||
let (event_tx, mut event_rx) = mpsc::unbounded_channel();
|
||||
let (_runtime_tx, _runtime_control_tx, runtime_terminate_handle) = spawn_runtime(
|
||||
HashMap::new(),
|
||||
StoredValues::from_mode(StoreLoadMode::Enabled),
|
||||
execute_request("while (true) {}"),
|
||||
event_tx,
|
||||
PendingRuntimeMode::Continue,
|
||||
@@ -493,7 +495,7 @@ mod tests {
|
||||
async fn pending_mode_freezes_runtime_commands_until_resume() {
|
||||
let (event_tx, mut event_rx) = mpsc::unbounded_channel();
|
||||
let (runtime_tx, runtime_control_tx, _runtime_terminate_handle) = spawn_runtime(
|
||||
HashMap::new(),
|
||||
StoredValues::from_mode(StoreLoadMode::Enabled),
|
||||
execute_request(
|
||||
r#"
|
||||
await new Promise((resolve) => setTimeout(resolve, 60_000));
|
||||
|
||||
@@ -18,6 +18,7 @@ use tokio::task::JoinSet;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::CodeModeSessionConfig;
|
||||
use crate::FunctionCallOutputContentItem;
|
||||
use crate::runtime::CodeModeNestedToolCall;
|
||||
use crate::runtime::DEFAULT_EXEC_YIELD_TIME_MS;
|
||||
@@ -33,6 +34,7 @@ use crate::runtime::WaitRequest;
|
||||
use crate::runtime::WaitToPendingOutcome;
|
||||
use crate::runtime::WaitToPendingRequest;
|
||||
use crate::runtime::spawn_runtime;
|
||||
use crate::stored_values::StoredValues;
|
||||
|
||||
pub type CodeModeSessionResultFuture<'a, T> =
|
||||
Pin<Box<dyn Future<Output = Result<T, String>> + Send + 'a>>;
|
||||
@@ -152,6 +154,7 @@ pub trait CodeModeSession: Send + Sync {
|
||||
pub trait CodeModeSessionProvider: Send + Sync {
|
||||
fn create_session<'a>(
|
||||
&'a self,
|
||||
config: CodeModeSessionConfig,
|
||||
delegate: Arc<dyn CodeModeSessionDelegate>,
|
||||
) -> CodeModeSessionProviderFuture<'a>;
|
||||
}
|
||||
@@ -162,11 +165,12 @@ pub struct InProcessCodeModeSessionProvider;
|
||||
impl CodeModeSessionProvider for InProcessCodeModeSessionProvider {
|
||||
fn create_session<'a>(
|
||||
&'a self,
|
||||
config: CodeModeSessionConfig,
|
||||
delegate: Arc<dyn CodeModeSessionDelegate>,
|
||||
) -> CodeModeSessionProviderFuture<'a> {
|
||||
Box::pin(async move {
|
||||
let session: Arc<dyn CodeModeSession> =
|
||||
Arc::new(CodeModeService::with_delegate(delegate));
|
||||
Arc::new(CodeModeService::with_delegate_and_config(delegate, config));
|
||||
Ok(session)
|
||||
})
|
||||
}
|
||||
@@ -180,7 +184,7 @@ struct CellHandle {
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
stored_values: Mutex<HashMap<String, JsonValue>>,
|
||||
stored_values: Mutex<StoredValues>,
|
||||
cells: Mutex<HashMap<CellId, CellHandle>>,
|
||||
delegate: Arc<dyn CodeModeSessionDelegate>,
|
||||
shutting_down: AtomicBool,
|
||||
@@ -193,13 +197,24 @@ pub struct CodeModeService {
|
||||
|
||||
impl CodeModeService {
|
||||
pub fn new() -> Self {
|
||||
Self::with_delegate(Arc::new(NoopCodeModeSessionDelegate))
|
||||
Self::with_config(CodeModeSessionConfig::default())
|
||||
}
|
||||
|
||||
pub fn with_config(config: CodeModeSessionConfig) -> Self {
|
||||
Self::with_delegate_and_config(Arc::new(NoopCodeModeSessionDelegate), config)
|
||||
}
|
||||
|
||||
pub fn with_delegate(delegate: Arc<dyn CodeModeSessionDelegate>) -> Self {
|
||||
Self::with_delegate_and_config(delegate, CodeModeSessionConfig::default())
|
||||
}
|
||||
|
||||
pub fn with_delegate_and_config(
|
||||
delegate: Arc<dyn CodeModeSessionDelegate>,
|
||||
config: CodeModeSessionConfig,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Inner {
|
||||
stored_values: Mutex::new(HashMap::new()),
|
||||
stored_values: Mutex::new(StoredValues::from_mode(config.store_load)),
|
||||
cells: Mutex::new(HashMap::new()),
|
||||
delegate,
|
||||
shutting_down: AtomicBool::new(false),
|
||||
@@ -875,13 +890,16 @@ mod tests {
|
||||
use super::WaitToPendingOutcome;
|
||||
use super::WaitToPendingRequest;
|
||||
use super::run_cell_control;
|
||||
use crate::CodeModeSessionConfig;
|
||||
use crate::CodeModeToolKind;
|
||||
use crate::FunctionCallOutputContentItem;
|
||||
use crate::StoreLoadMode;
|
||||
use crate::ToolDefinition;
|
||||
use crate::runtime::ExecuteRequest;
|
||||
use crate::runtime::ExecuteToPendingOutcome;
|
||||
use crate::runtime::RuntimeEvent;
|
||||
use crate::runtime::spawn_runtime;
|
||||
use crate::stored_values::StoredValues;
|
||||
|
||||
fn execute_request(source: &str) -> ExecuteRequest {
|
||||
ExecuteRequest {
|
||||
@@ -909,7 +927,7 @@ mod tests {
|
||||
|
||||
fn test_inner() -> Arc<Inner> {
|
||||
Arc::new(Inner {
|
||||
stored_values: Mutex::new(HashMap::new()),
|
||||
stored_values: Mutex::new(StoredValues::from_mode(StoreLoadMode::Enabled)),
|
||||
cells: Mutex::new(HashMap::new()),
|
||||
delegate: Arc::new(NoopCodeModeSessionDelegate),
|
||||
shutting_down: std::sync::atomic::AtomicBool::new(false),
|
||||
@@ -1007,6 +1025,44 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn disabled_store_load_does_not_inject_globals_or_allocate_json_storage() {
|
||||
let service = CodeModeService::with_config(CodeModeSessionConfig {
|
||||
store_load: StoreLoadMode::Disabled,
|
||||
});
|
||||
|
||||
let response = execute(
|
||||
&service,
|
||||
ExecuteRequest {
|
||||
source: r#"
|
||||
text(JSON.stringify([
|
||||
Object.hasOwn(globalThis, "store"),
|
||||
Object.hasOwn(globalThis, "load"),
|
||||
]));
|
||||
"#
|
||||
.to_string(),
|
||||
yield_time_ms: None,
|
||||
..execute_request("")
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
response,
|
||||
RuntimeResponse::Result {
|
||||
cell_id: cell_id("1"),
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "[false,false]".to_string(),
|
||||
}],
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
*service.inner.stored_values.lock().await,
|
||||
StoredValues::Disabled
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shutdown_interrupts_cpu_bound_cells() {
|
||||
let service = CodeModeService::new();
|
||||
@@ -1809,7 +1865,7 @@ image({
|
||||
let (initial_response_tx, initial_response_rx) = oneshot::channel();
|
||||
let (runtime_event_tx, _runtime_event_rx) = mpsc::unbounded_channel();
|
||||
let (runtime_tx, runtime_control_tx, runtime_terminate_handle) = spawn_runtime(
|
||||
HashMap::new(),
|
||||
StoredValues::from_mode(StoreLoadMode::Enabled),
|
||||
ExecuteRequest {
|
||||
source: "await new Promise(() => {})".to_string(),
|
||||
yield_time_ms: None,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
use crate::StoreLoadMode;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default)]
|
||||
pub(crate) enum StoredValues {
|
||||
Enabled(HashMap<String, JsonValue>),
|
||||
#[default]
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl StoredValues {
|
||||
pub(crate) fn from_mode(mode: StoreLoadMode) -> Self {
|
||||
match mode {
|
||||
StoreLoadMode::Enabled => Self::Enabled(HashMap::new()),
|
||||
StoreLoadMode::Disabled => Self::Disabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn empty_like(&self) -> Self {
|
||||
match self {
|
||||
Self::Enabled(_) => Self::Enabled(HashMap::new()),
|
||||
Self::Disabled => Self::Disabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_enabled(&self) -> bool {
|
||||
matches!(self, Self::Enabled(_))
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&mut self, key: String, value: JsonValue) {
|
||||
if let Self::Enabled(values) = self {
|
||||
values.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, key: &str) -> Option<&JsonValue> {
|
||||
match self {
|
||||
Self::Enabled(values) => values.get(key),
|
||||
Self::Disabled => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extend(&mut self, writes: Self) {
|
||||
match (self, writes) {
|
||||
(Self::Enabled(values), Self::Enabled(writes)) => values.extend(writes),
|
||||
(Self::Disabled, Self::Disabled) => {}
|
||||
(Self::Enabled(_), Self::Disabled) | (Self::Disabled, Self::Enabled(_)) => {
|
||||
debug_assert!(false, "stored value modes must match");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user