use serde::{Deserialize, Serialize};
use std::fmt;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IdsUri(String);
impl IdsUri {
pub fn new(uri: impl Into<String>) -> Result<Self, IdsError> {
let uri = uri.into();
if !uri.starts_with("http://") && !uri.starts_with("https://") && !uri.starts_with("urn:") {
return Err(IdsError::InvalidUri(format!(
"Invalid IDS URI format: {}",
uri
)));
}
Ok(Self(uri))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
impl fmt::Display for IdsUri {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl TryFrom<String> for IdsUri {
type Error = IdsError;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::new(s)
}
}
impl From<IdsUri> for String {
fn from(uri: IdsUri) -> Self {
uri.0
}
}
#[derive(Debug, Error)]
pub enum IdsError {
#[error("Invalid URI: {0}")]
InvalidUri(String),
#[error("Policy violation: {0}")]
PolicyViolation(String),
#[error("Contract negotiation failed: {0}")]
NegotiationFailed(String),
#[error("Contract not found: {0}")]
ContractNotFound(String),
#[error("Invalid contract state: expected {expected}, got {actual}")]
InvalidContractState { expected: String, actual: String },
#[error("DAPS authentication failed: {0}")]
DapsAuthFailed(String),
#[error("Invalid token: {0}")]
InvalidToken(String),
#[error("Trust verification failed: {0}")]
TrustVerificationFailed(String),
#[error("Data residency violation: {0}")]
ResidencyViolation(String),
#[error("GDPR compliance violation: {0}")]
GdprViolation(String),
#[error("Lineage tracking failed: {0}")]
LineageTrackingFailed(String),
#[error("Catalog error: {0}")]
CatalogError(String),
#[error("Message protocol error: {0}")]
MessageProtocolError(String),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Internal error: {0}")]
InternalError(String),
#[error("Contract not agreed: {0}")]
ContractNotAgreed(String),
#[error("Contract expired: {0}")]
ContractExpired(String),
#[error("Not found: {0}")]
NotFound(String),
#[error("Transfer failed: {0}")]
TransferFailed(String),
}
impl IdsError {
pub fn status_code(&self) -> u16 {
match self {
IdsError::InvalidUri(_) => 400,
IdsError::PolicyViolation(_) => 403,
IdsError::NegotiationFailed(_) => 422,
IdsError::ContractNotFound(_) => 404,
IdsError::InvalidContractState { .. } => 409,
IdsError::DapsAuthFailed(_) => 401,
IdsError::InvalidToken(_) => 401,
IdsError::TrustVerificationFailed(_) => 403,
IdsError::ResidencyViolation(_) => 451, IdsError::GdprViolation(_) => 451,
IdsError::LineageTrackingFailed(_) => 500,
IdsError::CatalogError(_) => 500,
IdsError::MessageProtocolError(_) => 400,
IdsError::SerializationError(_) => 500,
IdsError::InternalError(_) => 500,
IdsError::ContractNotAgreed(_) => 409,
IdsError::ContractExpired(_) => 410, IdsError::NotFound(_) => 404,
IdsError::TransferFailed(_) => 502,
}
}
}
pub type IdsResult<T> = Result<T, IdsError>;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Party {
pub id: IdsUri,
pub name: String,
pub legal_name: Option<String>,
pub description: Option<String>,
pub contact: Option<ContactInfo>,
pub gaiax_participant_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ContactInfo {
pub email: Option<String>,
pub phone: Option<String>,
pub address: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum SecurityProfile {
BaseSecurityProfile,
TrustSecurityProfile,
TrustPlusSecurityProfile,
}
impl SecurityProfile {
pub fn to_uri(&self) -> &'static str {
match self {
SecurityProfile::BaseSecurityProfile => {
"https://w3id.org/idsa/code/BASE_SECURITY_PROFILE"
}
SecurityProfile::TrustSecurityProfile => {
"https://w3id.org/idsa/code/TRUST_SECURITY_PROFILE"
}
SecurityProfile::TrustPlusSecurityProfile => {
"https://w3id.org/idsa/code/TRUST_PLUS_SECURITY_PROFILE"
}
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum TransferProtocol {
Https,
Idscp2,
MultipartFormData,
S3,
Kafka,
Nats,
}
impl TransferProtocol {
pub fn to_uri(&self) -> &'static str {
match self {
TransferProtocol::Https => "https://w3id.org/idsa/code/HTTPS",
TransferProtocol::Idscp2 => "https://w3id.org/idsa/code/IDSCP2",
TransferProtocol::MultipartFormData => "https://w3id.org/idsa/code/MULTIPART_FORM_DATA",
TransferProtocol::S3 => "https://w3id.org/idsa/code/S3",
TransferProtocol::Kafka => "https://w3id.org/idsa/code/KAFKA",
TransferProtocol::Nats => "https://w3id.org/idsa/code/NATS",
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum AdequacyStatus {
Adequate,
NotAdequate,
UnderReview,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct Jurisdiction {
pub country_code: String,
pub legal_framework: Vec<String>,
pub data_protection_authority: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ids_uri_creation() {
let uri = IdsUri::new("https://example.org/resource/123").unwrap();
assert_eq!(uri.as_str(), "https://example.org/resource/123");
let uri = IdsUri::new("urn:ids:resource:456").unwrap();
assert_eq!(uri.as_str(), "urn:ids:resource:456");
let result = IdsUri::new("invalid-uri");
assert!(result.is_err());
}
#[test]
fn test_security_profile_uri() {
assert_eq!(
SecurityProfile::BaseSecurityProfile.to_uri(),
"https://w3id.org/idsa/code/BASE_SECURITY_PROFILE"
);
assert_eq!(
SecurityProfile::TrustPlusSecurityProfile.to_uri(),
"https://w3id.org/idsa/code/TRUST_PLUS_SECURITY_PROFILE"
);
}
#[test]
fn test_security_profile_ordering() {
assert!(SecurityProfile::BaseSecurityProfile < SecurityProfile::TrustSecurityProfile);
assert!(SecurityProfile::TrustSecurityProfile < SecurityProfile::TrustPlusSecurityProfile);
}
#[test]
fn test_transfer_protocol_uri() {
assert_eq!(
TransferProtocol::Https.to_uri(),
"https://w3id.org/idsa/code/HTTPS"
);
assert_eq!(
TransferProtocol::Idscp2.to_uri(),
"https://w3id.org/idsa/code/IDSCP2"
);
}
}