openrtc 2.8.5

OpenRTC: a Rust-first P2P runtime for device discovery, signaling, and iroh/QUIC networking.
Documentation
//! Public service denial metadata. This describes recovery; it never schedules it.
use serde::Serialize;
use serde_json::Value;

#[path = "service_error_codes.rs"]
mod codes;
pub use codes::{ERROR_DOCUMENTATION_URL, SERVICE_ERROR_CODES};

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceError {
    pub code: String,
    pub retryable: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scope: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub operation: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub request_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub retry_after_ms: Option<u64>,
    /// UTC Unix milliseconds, compatible with the browser's safe integer range.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reset_at: Option<u64>,
    pub documentation_url: &'static str,
}

impl ServiceError {
    /// Find recognized service metadata through native runtime context layers.
    /// The returned view contains no response body, credentials, or provider costs.
    #[cfg(not(target_arch = "wasm32"))]
    pub fn from_error(error: &anyhow::Error) -> Option<&Self> {
        error.chain().find_map(|source| {
            source.downcast_ref::<crate::native::ControlPlaneHttpError>()
                .and_then(|error| error.service_error.as_ref())
                .or_else(|| source.downcast_ref::<crate::native_coordination_gateway::GatewayConnectError>()
                    .and_then(|error| error.service_error.as_ref()))
        })
    }

    /// Ignore unknown service codes and do not retain arbitrary response fields.
    pub fn from_value(value: &Value) -> Option<Self> {
        let code = value.get("code")?.as_str()?;
        let normalized = code.trim().to_ascii_lowercase();
        let alias = codes::LEGACY_SERVICE_ERROR_ALIASES.iter()
            .find(|(legacy, _, _)| *legacy == normalized);
        if !SERVICE_ERROR_CODES.contains(&code) && alias.is_none() {
            return None;
        }
        let bounded = |key: &str, maximum: usize, valid: fn(u8) -> bool| {
            value
                .get(key)
                .and_then(Value::as_str)
                .filter(|v| !v.is_empty() && v.len() <= maximum && v.bytes().all(valid))
                .map(str::to_owned)
        };
        let timing = |key: &str| {
            value
                .get(key)
                .and_then(Value::as_u64)
                .filter(|v| *v <= 9_007_199_254_740_991)
        };
        Some(Self {
            code: alias.map_or(code, |(_, canonical, _)| canonical).to_owned(),
            // Canonical denials without an explicit retry grant are terminal.
            // Known legacy throttles retain their old retryable behavior.
            retryable: value
                .get("retryable")
                .and_then(Value::as_bool)
                .unwrap_or(alias.is_some()),
            scope: alias.map(|(_, _, scope)| (*scope).to_owned()).or_else(|| value
                .get("scope")
                .and_then(Value::as_str)
                .filter(|v| ["account", "app", "principal", "ip", "avenue", "platform"].contains(v))
                .map(str::to_owned)),
            operation: bounded("operation", 100, |b| {
                b.is_ascii_lowercase() || b.is_ascii_digit() || b".-".contains(&b)
            }),
            request_id: bounded("requestId", 160, |b| {
                b.is_ascii_alphanumeric() || b"_.:-".contains(&b)
            }),
            retry_after_ms: timing("retryAfterMs"),
            reset_at: timing("resetAt"),
            documentation_url: ERROR_DOCUMENTATION_URL,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn legacy_edge_denials_keep_canonical_scope_and_recovery_metadata() {
        for (legacy, canonical, scope) in codes::LEGACY_SERVICE_ERROR_ALIASES {
            let minimal = ServiceError::from_value(&json!({"code": legacy})).unwrap();
            assert_eq!(minimal.code, *canonical);
            assert_eq!(minimal.scope.as_deref(), Some(*scope));
            assert!(minimal.retryable);
            assert_eq!(minimal.retry_after_ms, None);
            assert_eq!(minimal.reset_at, None);
            let error = ServiceError::from_value(&json!({"code": legacy, "retryable": false,
                "scope": "app", "operation": "gateway.grant.issue", "requestId": "legacy-1",
                "retryAfterMs": 60000, "resetAt": 1800000000000_u64,
                "documentationUrl": "https://untrusted.invalid", "providerCost": 99})).unwrap();
            let serialized = serde_json::to_value(error).unwrap();
            assert_eq!(serialized["code"], *canonical);
            assert_eq!(serialized["scope"], *scope);
            assert_eq!(serialized["retryable"], false);
            assert_eq!(serialized["retryAfterMs"], 60000);
            assert_eq!(serialized["resetAt"], 1800000000000_u64);
            assert_eq!(serialized["requestId"], "legacy-1");
            assert_eq!(serialized["documentationUrl"], ERROR_DOCUMENTATION_URL);
            assert!(serialized.get("providerCost").is_none());
        }
    }

    #[test]
    fn service_error_metadata_is_bounded_and_preserves_all_codes() {
        for code in SERVICE_ERROR_CODES {
            let error = ServiceError::from_value(&json!({"code": code, "retryable": true,
                "scope": "app", "operation": "gateway.grant.issue", "requestId": "request-1",
                "retryAfterMs": 2300, "resetAt": 1800000000000_u64,
                "documentationUrl": "https://untrusted.invalid", "providerCost": 99}))
            .unwrap();
            assert_eq!(error.code, *code);
            assert!(error.retryable);
            assert_eq!(error.retry_after_ms, Some(2300));
            assert_eq!(error.reset_at, Some(1800000000000));
            let serialized = serde_json::to_value(error).unwrap();
            assert_eq!(serialized["documentationUrl"], ERROR_DOCUMENTATION_URL);
            assert!(serialized.get("providerCost").is_none());
        }
        let invalid =
            ServiceError::from_value(&json!({"code": "app-rate-limited", "scope": "private",
            "operation": "bad\nvalue", "requestId": "bad token", "retryAfterMs": -1,
            "resetAt": 9007199254740992_u64}))
            .unwrap();
        assert!(!invalid.retryable);
        assert!(
            invalid.scope.is_none() && invalid.operation.is_none() && invalid.request_id.is_none()
        );
        assert!(invalid.retry_after_ms.is_none() && invalid.reset_at.is_none());
        assert!(ServiceError::from_value(&json!({"code": "unknown"})).is_none());
    }
}