codex-mobile-bridge 0.3.0

Remote bridge and service manager for codex-mobile.
Documentation
use anyhow::{Context, Result};
use serde_json::Value;

pub(super) fn request_key(id: &Value) -> String {
    match id {
        Value::String(value) => value.clone(),
        _ => id.to_string(),
    }
}

pub(super) fn optional_string(value: &Value, key: &str) -> Option<String> {
    value
        .get(key)
        .and_then(Value::as_str)
        .map(ToOwned::to_owned)
}

pub(super) fn normalize_optional_text(value: Option<String>) -> Option<String> {
    value.and_then(|value| {
        let trimmed = value.trim();
        if trimmed.is_empty() {
            None
        } else {
            Some(trimmed.to_string())
        }
    })
}

pub(super) fn normalize_name(name: Option<String>) -> Option<String> {
    normalize_optional_text(name)
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum OptionalTextUpdate {
    Unchanged,
    Clear,
    Set(String),
}

pub(super) fn resolve_optional_text_update(
    current: Option<&str>,
    requested: Option<&str>,
) -> OptionalTextUpdate {
    let current = normalize_optional_text(current.map(str::to_string));
    match normalize_optional_text(requested.map(str::to_string)) {
        Some(next) => {
            if current.as_deref() == Some(next.as_str()) {
                OptionalTextUpdate::Unchanged
            } else {
                OptionalTextUpdate::Set(next)
            }
        }
        None if requested.is_none() => OptionalTextUpdate::Unchanged,
        None if current.is_some() => OptionalTextUpdate::Clear,
        None => OptionalTextUpdate::Unchanged,
    }
}

pub(super) fn required_string<'a>(value: &'a Value, key: &str) -> Result<&'a str> {
    value
        .get(key)
        .and_then(Value::as_str)
        .with_context(|| format!("缺少字段 {key}"))
}