use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceEndpoint {
pub id: String,
#[serde(rename = "type")]
pub service_type: String,
pub service_endpoint: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub properties: Option<BTreeMap<String, String>>,
}
pub mod service_types {
pub const OAS_ENTITY: &str = "OASEntity";
pub const OAS_COMMUNICATION: &str = "OASCommunication";
pub const OAS_LINEAGE_QUERY: &str = "OASLineageQuery";
pub const OAS_ATTESTATION: &str = "OASAttestationService";
pub const OAS_REVOCATION_STATUS: &str = "OASRevocationStatus";
pub const OAS_CAPABILITY_INVOCATION: &str = "OASCapabilityInvocation";
pub const OAS_GOVERNANCE: &str = "OASGovernance";
pub const OAS_PRIVATE_ENDPOINT: &str = "OASPrivateEndpointService";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde_round_trip() {
let svc = ServiceEndpoint {
id: "did:oas:ns:agent:bot#service-1".to_string(),
service_type: "OASEntity".to_string(),
service_endpoint: "https://example.com/bot".to_string(),
label: None,
description: None,
properties: None,
};
let json = serde_json::to_string(&svc).unwrap_or_default();
let parsed: ServiceEndpoint = serde_json::from_str(&json).unwrap_or_else(|_| svc.clone());
assert_eq!(svc, parsed);
}
#[test]
fn backward_compat_3_fields_only() {
let json = r#"{"id":"did:oas:ns:agent:bot#s1","type":"OASEntity","serviceEndpoint":"https://example.com"}"#;
let svc: Result<ServiceEndpoint, _> = serde_json::from_str(json);
assert!(svc.is_ok());
let svc = svc.unwrap_or_else(|_| ServiceEndpoint {
id: String::new(),
service_type: String::new(),
service_endpoint: String::new(),
label: None,
description: None,
properties: None,
});
assert!(svc.label.is_none());
assert!(svc.properties.is_none());
}
#[test]
fn with_properties() {
let mut props = BTreeMap::new();
props.insert("apiVersion".to_string(), "v2".to_string());
props.insert("authScheme".to_string(), "did-auth".to_string());
let svc = ServiceEndpoint {
id: "did:oas:ns:agent:bot#api".to_string(),
service_type: service_types::OAS_ENTITY.to_string(),
service_endpoint: "https://example.com/bot/v2".to_string(),
label: Some("API v2".to_string()),
description: Some("Production API endpoint".to_string()),
properties: Some(props),
};
let json = serde_json::to_string_pretty(&svc).unwrap_or_default();
let parsed: ServiceEndpoint = serde_json::from_str(&json).unwrap_or_else(|_| svc.clone());
assert_eq!(svc, parsed);
assert!(json.contains("apiVersion"));
}
#[test]
fn private_endpoint_service_type() {
let svc = ServiceEndpoint {
id: "did:oas:ns:agent:bot#private".to_string(),
service_type: service_types::OAS_PRIVATE_ENDPOINT.to_string(),
service_endpoint: "https://example.com/bot/private".to_string(),
label: Some("Private field resolver".to_string()),
description: None,
properties: None,
};
assert_eq!(svc.service_type, "OASPrivateEndpointService");
}
}