use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Metadata {
pub timestamp: String,
pub sha256: String,
pub size: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_frequency: Option<u32>,
pub entries: usize,
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PoliciesMetadata {
pub allow_upload: bool,
#[serde(default)]
pub schema_validation_mode: String,
pub policies: Metadata,
pub labels: Metadata,
#[serde(default)]
pub schema: Option<Metadata>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct RequestLimits {
pub max_context_bytes: usize,
pub max_context_depth: usize,
pub max_context_keys: usize,
}
impl Default for RequestLimits {
fn default() -> Self {
Self {
max_context_bytes: 16 * 1024,
max_context_depth: 8,
max_context_keys: 64,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum RequestContextFallbackReason {
NoSchema,
SchemaIncompatible,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct RequestContextStatus {
pub supported: bool,
pub schema_backed: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fallback_reason: Option<RequestContextFallbackReason>,
}
impl Default for RequestContextStatus {
fn default() -> Self {
Self {
supported: true,
schema_backed: false,
fallback_reason: Some(RequestContextFallbackReason::NoSchema),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct StatusResponse {
pub policy_configuration: PoliciesMetadata,
pub parallel_configuration: serde_json::Value,
#[serde(default)]
pub request_limits: RequestLimits,
#[serde(default)]
pub request_context: RequestContextStatus,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_response_deserialization_v006() {
let json = serde_json::json!({
"policy_configuration": {
"allow_upload": true,
"schema_validation_mode": "permissive",
"policies": {
"timestamp": "2026-01-01T00:00:00Z",
"sha256": "abc123",
"size": 1024,
"source": "https://example.com/policies",
"refresh_frequency": 60,
"entries": 5,
"content": "permit(...)"
},
"labels": {
"timestamp": "2026-01-01T00:00:00Z",
"sha256": "def456",
"size": 256,
"entries": 2,
"content": "labels"
},
"schema": {
"timestamp": "2026-01-01T00:00:00Z",
"sha256": "",
"size": 0,
"entries": 0,
"content": ""
}
},
"parallel_configuration": {
"workers": 2,
"cpu_count": 8,
"rayon_threads": 4,
"par_threshold": 8,
"allow_parallel": true
},
"request_limits": {
"max_context_bytes": 16384,
"max_context_depth": 8,
"max_context_keys": 64
},
"request_context": {
"supported": true,
"schema_backed": false,
"fallback_reason": "no_schema"
}
});
let status: StatusResponse = serde_json::from_value(json).unwrap();
assert!(status.policy_configuration.allow_upload);
assert_eq!(
status.policy_configuration.schema_validation_mode,
"permissive"
);
assert_eq!(status.policy_configuration.policies.entries, 5);
assert!(status.policy_configuration.schema.is_some());
assert_eq!(status.request_limits.max_context_bytes, 16384);
assert!(status.request_context.supported);
assert!(!status.request_context.schema_backed);
assert_eq!(
status.request_context.fallback_reason,
Some(RequestContextFallbackReason::NoSchema)
);
}
#[test]
fn status_response_backward_compat_v004() {
let json = serde_json::json!({
"policy_configuration": {
"allow_upload": true,
"policies": {
"timestamp": "2026-01-01T00:00:00Z",
"sha256": "abc",
"size": 100,
"entries": 3,
"content": "permit(...);"
},
"labels": {
"timestamp": "2026-01-01T00:00:00Z",
"sha256": "def",
"size": 0,
"entries": 0,
"content": ""
}
},
"parallel_configuration": { "cpu_count": 4 }
});
let status: StatusResponse = serde_json::from_value(json).unwrap();
assert!(status.policy_configuration.allow_upload);
assert_eq!(status.policy_configuration.schema_validation_mode, "");
assert!(status.policy_configuration.schema.is_none());
assert_eq!(status.request_limits, RequestLimits::default());
assert_eq!(status.request_context, RequestContextStatus::default());
}
#[test]
fn request_limits_default() {
let limits = RequestLimits::default();
assert_eq!(limits.max_context_bytes, 16 * 1024);
assert_eq!(limits.max_context_depth, 8);
assert_eq!(limits.max_context_keys, 64);
}
#[test]
fn request_context_status_default() {
let status = RequestContextStatus::default();
assert!(status.supported);
assert!(!status.schema_backed);
assert_eq!(
status.fallback_reason,
Some(RequestContextFallbackReason::NoSchema)
);
}
#[test]
fn unknown_context_fallback_reason_is_forward_compatible() {
let reason: RequestContextFallbackReason =
serde_json::from_value(serde_json::json!("future_reason")).unwrap();
assert_eq!(reason, RequestContextFallbackReason::Unknown);
}
}