use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use trust_tasks_rs::Payload;
use trust_tasks_rs::specs::registry;
use crate::domain::{Action, AuthorityId, EntityId, Resource};
use crate::storage::repository::TrustRecordQuery;
pub use registry::authorization::v0_1::{
Payload as AuthorizationRequest, Response as AuthorizationResponse,
ResponseMessage as AuthorizationResponseMessage,
};
pub use registry::recognition::v0_1::{
Payload as RecognitionRequest, Response as RecognitionResponse,
ResponseMessage as RecognitionResponseMessage,
};
pub use registry::record::delete::v0_1::{
Payload as RecordDeleteRequest, Response as RecordDeleteResponse,
};
pub use registry::record::create::v0_1::TrustRecord as SpecTrustRecord;
pub mod type_uris {
use super::*;
use trust_tasks_rs::Payload;
pub const RECOGNITION: &str = RecognitionRequest::TYPE_URI;
pub const AUTHORIZATION: &str = AuthorizationRequest::TYPE_URI;
pub const RECORD_PUT: &str = RecordPutRequest::TYPE_URI;
pub const RECORD_QUERY: &str = RecordQueryRequest::TYPE_URI;
pub const RECORD_DELETE: &str = RecordDeleteRequest::TYPE_URI;
pub const DID_ROTATE: &str = "https://trusttasks.org/spec/registry/did/rotate/0.1";
}
pub(crate) fn query_of(
entity_id: &str,
authority_id: &str,
action: &str,
resource: &str,
) -> TrustRecordQuery {
TrustRecordQuery::new(
EntityId::new(entity_id),
AuthorityId::new(authority_id),
Action::new(action),
Resource::new(resource),
)
}
pub(crate) fn reserialize<A, B>(value: &A) -> Result<B, String>
where
A: Serialize,
B: DeserializeOwned,
{
let json = serde_json::to_value(value).map_err(|e| e.to_string())?;
serde_json::from_value(json).map_err(|e| e.to_string())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecordPutRequest {
pub record: SpecTrustRecord,
#[serde(
rename = "expectedExisting",
default,
skip_serializing_if = "Option::is_none"
)]
pub expected_existing: Option<bool>,
}
impl Payload for RecordPutRequest {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/registry/record/put/0.1";
const IS_PROOF_REQUIRED: bool = true;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecordPutResponse {
pub ok: bool,
pub created: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
impl Payload for RecordPutResponse {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/registry/record/put/0.1#response";
const IS_PROOF_REQUIRED: bool = true;
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RecordQueryRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub entity_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub authority_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub action: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
}
impl Payload for RecordQueryRequest {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/registry/record/query/0.1";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecordQueryResponse {
pub records: Vec<SpecTrustRecord>,
#[serde(
rename = "nextCursor",
default,
skip_serializing_if = "Option::is_none"
)]
pub next_cursor: Option<String>,
}
impl Payload for RecordQueryResponse {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/registry/record/query/0.1#response";
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct DidRotateRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pre_rotation_count: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
impl Payload for DidRotateRequest {
const TYPE_URI: &'static str = type_uris::DID_ROTATE;
const IS_PROOF_REQUIRED: bool = true;
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DidRotateResponse {
pub did: String,
pub new_scid: String,
pub new_version_id: String,
}
impl Payload for DidRotateResponse {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/registry/did/rotate/0.1#response";
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::{Action, AuthorityId, EntityId, RecordType, Resource, TrustRecordBuilder};
use trust_tasks_rs::Payload;
#[test]
fn type_uris_parse_as_valid_type_uris() {
let _ = RecognitionRequest::type_uri();
let _ = AuthorizationRequest::type_uri();
let _ = RecordPutRequest::type_uri();
let _ = RecordQueryRequest::type_uri();
let _ = RecordDeleteRequest::type_uri();
let _ = DidRotateRequest::type_uri();
let _ = DidRotateResponse::type_uri();
}
#[test]
fn type_uri_constants_match_the_spec_slugs() {
assert_eq!(type_uris::RECOGNITION, RecognitionRequest::TYPE_URI);
assert_eq!(
RecognitionRequest::type_uri().slug(),
"registry/recognition"
);
assert_eq!(RecordPutRequest::type_uri().slug(), "registry/record/put");
assert_eq!(
RecordQueryRequest::type_uri().slug(),
"registry/record/query"
);
}
#[test]
fn writes_require_proof_reads_do_not() {
assert!(RecordPutRequest::IS_PROOF_REQUIRED);
assert!(RecordDeleteRequest::IS_PROOF_REQUIRED);
assert!(DidRotateRequest::IS_PROOF_REQUIRED);
assert!(!RecordQueryRequest::IS_PROOF_REQUIRED);
assert!(!RecognitionRequest::IS_PROOF_REQUIRED);
assert!(!AuthorizationRequest::IS_PROOF_REQUIRED);
}
#[test]
fn put_and_query_use_the_spec_wire_field_names() {
let put: RecordPutRequest = serde_json::from_value(serde_json::json!({
"record": {
"entity_id": "did:example:entity",
"authority_id": "did:example:authority",
"action": "issue",
"resource": "vc",
"record_type": "authorization"
},
"expectedExisting": false
}))
.expect("parses");
assert_eq!(put.expected_existing, Some(false));
let put_json = serde_json::to_value(&put).expect("serialises");
assert!(put_json.get("expectedExisting").is_some());
let response = RecordQueryResponse {
records: vec![],
next_cursor: Some("50".into()),
};
let json = serde_json::to_value(&response).expect("serialises");
assert_eq!(json["nextCursor"], "50");
}
#[test]
fn recognition_request_uses_verbatim_trqp_field_names() {
let json = serde_json::json!({
"entity_id": "did:example:entity",
"authority_id": "did:example:authority",
"action": "issue",
"resource": "vc",
"context": { "time": "2026-07-06T00:00:00Z" }
});
let req: RecognitionRequest = serde_json::from_value(json).expect("parses");
assert_eq!(req.entity_id, "did:example:entity");
assert_eq!(req.action, "issue");
assert!(req.context.and_then(|c| c.time).is_some());
}
#[test]
fn domain_record_round_trips_to_and_from_the_spec_record() {
let domain = TrustRecordBuilder::new()
.entity_id(EntityId::new("did:example:entity"))
.authority_id(AuthorityId::new("did:example:authority"))
.action(Action::new("issue"))
.resource(Resource::new("vc"))
.recognized(true)
.authorized(true)
.record_type(RecordType::Authorization)
.build()
.expect("valid record");
let spec: SpecTrustRecord = reserialize(&domain).expect("domain -> spec");
assert_eq!(spec.entity_id, "did:example:entity");
assert_eq!(spec.record_type.to_string(), "authorization");
let back: crate::domain::TrustRecord = reserialize(&spec).expect("spec -> domain");
assert_eq!(back, domain);
}
#[test]
fn query_of_builds_the_repository_query() {
let q = query_of("e", "a", "act", "res");
assert_eq!(q.entity_id.as_str(), "e");
assert_eq!(q.authority_id.as_str(), "a");
assert_eq!(q.action.as_str(), "act");
assert_eq!(q.resource.as_str(), "res");
}
}