//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `vta/backup/complete-export`. Version: `1.0`.
#[allow(unused_imports)]
use serde::{Deserialize, Serialize};
/// Error types.
pub mod error {
/// Error from a `TryFrom` or `FromStr` implementation.
pub struct ConversionError(::std::borrow::Cow<'static, str>);
impl ::std::error::Error for ConversionError {}
impl ::std::fmt::Display for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Display::fmt(&self.0, f)
}
}
impl ::std::fmt::Debug for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Debug::fmt(&self.0, f)
}
}
impl From<&'static str> for ConversionError {
fn from(value: &'static str) -> Self {
Self(value.into())
}
}
impl From<String> for ConversionError {
fn from(value: String) -> Self {
Self(value.into())
}
}
}
///Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Ext",
/// "description": "Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.",
/// "type": "object",
/// "minProperties": 1,
/// "additionalProperties": true,
/// "propertyNames": {
/// "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
/// }
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(transparent)]
pub struct Ext(pub ::std::collections::HashMap<ExtKey, ::serde_json::Value>);
impl ::std::ops::Deref for Ext {
type Target = ::std::collections::HashMap<ExtKey, ::serde_json::Value>;
fn deref(&self) -> &::std::collections::HashMap<ExtKey, ::serde_json::Value> {
&self.0
}
}
impl ::std::convert::From<Ext> for ::std::collections::HashMap<ExtKey, ::serde_json::Value> {
fn from(value: Ext) -> Self {
value.0
}
}
impl ::std::convert::From<::std::collections::HashMap<ExtKey, ::serde_json::Value>> for Ext {
fn from(value: ::std::collections::HashMap<ExtKey, ::serde_json::Value>) -> Self {
Self(value)
}
}
///`ExtKey`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ExtKey(::std::string::String);
impl ::std::ops::Deref for ExtKey {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ExtKey> for ::std::string::String {
fn from(value: ExtKey) -> Self {
value.0
}
}
impl ::std::str::FromStr for ExtKey {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new("^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$").unwrap()
});
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for ExtKey {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///Acknowledges that an export bundle was downloaded, so the recipient can release the staged bytes and retire the transport token. The outer document members (id, type, issuer, recipient, issuedAt, expiresAt, proof) are owned by the framework — SPEC §6.3.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/vta/backup/complete-export/1.0",
/// "title": "Payload",
/// "description": "Acknowledges that an export bundle was downloaded, so the recipient can release the staged bytes and retire the transport token. The outer document members (id, type, issuer, recipient, issuedAt, expiresAt, proof) are owned by the framework — SPEC §6.3.",
/// "type": "object",
/// "required": [
/// "bundleId"
/// ],
/// "properties": {
/// "bundleId": {
/// "description": "Handle from the initiate-export descriptor. Opaque: a producer quotes what it was given and must not derive, guess or enumerate one.",
/// "type": "string",
/// "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1.",
/// "$ref": "#/definitions/Ext"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Payload {
///Handle from the initiate-export descriptor. Opaque: a producer quotes what it was given and must not derive, guess or enumerate one.
#[serde(rename = "bundleId")]
pub bundle_id: PayloadBundleId,
///Ecosystem-defined extension members per SPEC.md §4.5.1.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
}
impl Payload {
pub fn builder() -> builder::Payload {
Default::default()
}
}
///Handle from the initiate-export descriptor. Opaque: a producer quotes what it was given and must not derive, guess or enumerate one.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Handle from the initiate-export descriptor. Opaque: a producer quotes what it was given and must not derive, guess or enumerate one.",
/// "type": "string",
/// "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadBundleId(::std::string::String);
impl ::std::ops::Deref for PayloadBundleId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadBundleId> for ::std::string::String {
fn from(value: PayloadBundleId) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadBundleId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new(
"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
)
.unwrap()
});
if PATTERN.find(value).is_none() {
return Err(
"doesn't match pattern \"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\""
.into(),
);
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadBundleId {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for PayloadBundleId {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for PayloadBundleId {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for PayloadBundleId {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///`Response`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "type": "object",
/// "required": [
/// "bundleId",
/// "downloaded"
/// ],
/// "properties": {
/// "bundleId": {
/// "description": "Echoed so the response stands alone as a record.",
/// "type": "string",
/// "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
/// },
/// "downloaded": {
/// "description": "Whether the byte stream was actually fetched before this acknowledgement. Required, not optional: false is the honest answer when the operator skipped the download or the slot expired first, and an absent member would let that be read as success.",
/// "type": "boolean"
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1.",
/// "$ref": "#/definitions/Ext"
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "response"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Response {
///Echoed so the response stands alone as a record.
#[serde(rename = "bundleId")]
pub bundle_id: ResponseBundleId,
///Whether the byte stream was actually fetched before this acknowledgement. Required, not optional: false is the honest answer when the operator skipped the download or the slot expired first, and an absent member would let that be read as success.
pub downloaded: bool,
///Ecosystem-defined extension members per SPEC.md §4.5.1.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
}
impl Response {
pub fn builder() -> builder::Response {
Default::default()
}
}
///Echoed so the response stands alone as a record.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Echoed so the response stands alone as a record.",
/// "type": "string",
/// "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ResponseBundleId(::std::string::String);
impl ::std::ops::Deref for ResponseBundleId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ResponseBundleId> for ::std::string::String {
fn from(value: ResponseBundleId) -> Self {
value.0
}
}
impl ::std::str::FromStr for ResponseBundleId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new(
"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
)
.unwrap()
});
if PATTERN.find(value).is_none() {
return Err(
"doesn't match pattern \"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\""
.into(),
);
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for ResponseBundleId {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for ResponseBundleId {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for ResponseBundleId {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for ResponseBundleId {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
/// Types for composing complex structures.
pub mod builder {
#[derive(Clone, Debug)]
pub struct Payload {
bundle_id: ::std::result::Result<super::PayloadBundleId, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
}
impl ::std::default::Default for Payload {
fn default() -> Self {
Self {
bundle_id: Err("no value supplied for bundle_id".to_string()),
ext: Ok(Default::default()),
}
}
}
impl Payload {
pub fn bundle_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::PayloadBundleId>,
T::Error: ::std::fmt::Display,
{
self.bundle_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for bundle_id: {e}"));
self
}
pub fn ext<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::Ext>>,
T::Error: ::std::fmt::Display,
{
self.ext = value
.try_into()
.map_err(|e| format!("error converting supplied value for ext: {e}"));
self
}
}
impl ::std::convert::TryFrom<Payload> for super::Payload {
type Error = super::error::ConversionError;
fn try_from(value: Payload) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
bundle_id: value.bundle_id?,
ext: value.ext?,
})
}
}
impl ::std::convert::From<super::Payload> for Payload {
fn from(value: super::Payload) -> Self {
Self {
bundle_id: Ok(value.bundle_id),
ext: Ok(value.ext),
}
}
}
#[derive(Clone, Debug)]
pub struct Response {
bundle_id: ::std::result::Result<super::ResponseBundleId, ::std::string::String>,
downloaded: ::std::result::Result<bool, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
}
impl ::std::default::Default for Response {
fn default() -> Self {
Self {
bundle_id: Err("no value supplied for bundle_id".to_string()),
downloaded: Err("no value supplied for downloaded".to_string()),
ext: Ok(Default::default()),
}
}
}
impl Response {
pub fn bundle_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::ResponseBundleId>,
T::Error: ::std::fmt::Display,
{
self.bundle_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for bundle_id: {e}"));
self
}
pub fn downloaded<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<bool>,
T::Error: ::std::fmt::Display,
{
self.downloaded = value
.try_into()
.map_err(|e| format!("error converting supplied value for downloaded: {e}"));
self
}
pub fn ext<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::Ext>>,
T::Error: ::std::fmt::Display,
{
self.ext = value
.try_into()
.map_err(|e| format!("error converting supplied value for ext: {e}"));
self
}
}
impl ::std::convert::TryFrom<Response> for super::Response {
type Error = super::error::ConversionError;
fn try_from(value: Response) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
bundle_id: value.bundle_id?,
downloaded: value.downloaded?,
ext: value.ext?,
})
}
}
impl ::std::convert::From<super::Response> for Response {
fn from(value: super::Response) -> Self {
Self {
bundle_id: Ok(value.bundle_id),
downloaded: Ok(value.downloaded),
ext: Ok(value.ext),
}
}
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/vta/backup/complete-export/1.0";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"Ext\": {\n \"additionalProperties\": true,\n \"description\": \"Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.\",\n \"minProperties\": 1,\n \"propertyNames\": {\n \"pattern\": \"^[a-z][a-z0-9-]*(\\\\.[a-z0-9-]+)+$\"\n },\n \"title\": \"Ext\",\n \"type\": \"object\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"properties\": {\n \"bundleId\": {\n \"description\": \"Echoed so the response stands alone as a record.\",\n \"pattern\": \"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\n \"type\": \"string\"\n },\n \"downloaded\": {\n \"description\": \"Whether the byte stream was actually fetched before this acknowledgement. Required, not optional: false is the honest answer when the operator skipped the download or the slot expired first, and an absent member would let that be read as success.\",\n \"type\": \"boolean\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n }\n },\n \"required\": [\n \"bundleId\",\n \"downloaded\"\n ],\n \"title\": \"VTA Backup Complete Export — response payload\",\n \"type\": \"object\"\n }\n },\n \"$id\": \"https://trusttasks.org/spec/vta/backup/complete-export/1.0\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"description\": \"Acknowledges that an export bundle was downloaded, so the recipient can release the staged bytes and retire the transport token. The outer document members (id, type, issuer, recipient, issuedAt, expiresAt, proof) are owned by the framework — SPEC §6.3.\",\n \"properties\": {\n \"bundleId\": {\n \"description\": \"Handle from the initiate-export descriptor. Opaque: a producer quotes what it was given and must not derive, guess or enumerate one.\",\n \"pattern\": \"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\n \"type\": \"string\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n }\n },\n \"required\": [\n \"bundleId\"\n ],\n \"title\": \"VTA Backup — Complete Export — payload\",\n \"type\": \"object\"\n}\n",
);
}
impl crate::Payload for Response {
const TYPE_URI: &'static str =
"https://trusttasks.org/spec/vta/backup/complete-export/1.0#response";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"Ext\": {\n \"additionalProperties\": true,\n \"description\": \"Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.\",\n \"minProperties\": 1,\n \"propertyNames\": {\n \"pattern\": \"^[a-z][a-z0-9-]*(\\\\.[a-z0-9-]+)+$\"\n },\n \"title\": \"Ext\",\n \"type\": \"object\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"properties\": {\n \"bundleId\": {\n \"description\": \"Echoed so the response stands alone as a record.\",\n \"pattern\": \"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\n \"type\": \"string\"\n },\n \"downloaded\": {\n \"description\": \"Whether the byte stream was actually fetched before this acknowledgement. Required, not optional: false is the honest answer when the operator skipped the download or the slot expired first, and an absent member would let that be read as success.\",\n \"type\": \"boolean\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n }\n },\n \"required\": [\n \"bundleId\",\n \"downloaded\"\n ],\n \"title\": \"VTA Backup Complete Export — response payload\",\n \"type\": \"object\"\n }\n },\n \"$ref\": \"#/$defs/Response\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\n}\n",
);
}
impl crate::RequestPayload for Payload {
type Response = Response;
}
#[cfg(test)]
mod conformance {
//! Round-trip tests harvested from the spec's `spec.md`,
//! plus a `rejects_invalid_examples` test for any fixtures
//! in `payload.invalid-examples.json` (validate feature).
#[test]
fn request_example_1() {
const JSON: &str = "{\n \"id\": \"urn:uuid:00000000-0000-4000-8000-000000000003\",\n \"type\": \"https://trusttasks.org/spec/vta/backup/complete-export/1.0#request\",\n \"issuer\": \"did:example:operator\",\n \"recipient\": \"did:example:agent\",\n \"issuedAt\": \"2026-01-01T00:02:00Z\",\n \"threadId\": \"urn:uuid:00000000-0000-4000-8000-0000000000ff\",\n \"payload\": {\n \"bundleId\": \"3f2504e0-4f89-41d3-9a0c-0305e82c3301\"\n }\n}\n";
let doc: crate::TrustTask<super::Payload> =
serde_json::from_str(JSON).expect("deserialize request example");
let rendered = serde_json::to_value(&doc).expect("re-serialize");
let expected: serde_json::Value = serde_json::from_str(JSON).expect("re-parse expected");
assert_eq!(rendered, expected, "request example failed round-trip");
}
#[test]
fn response_example_1() {
const JSON: &str = "{\n \"id\": \"urn:uuid:00000000-0000-4000-8000-000000000004\",\n \"type\": \"https://trusttasks.org/spec/vta/backup/complete-export/1.0#response\",\n \"issuer\": \"did:example:agent\",\n \"recipient\": \"did:example:operator\",\n \"issuedAt\": \"2026-01-01T00:02:01Z\",\n \"threadId\": \"urn:uuid:00000000-0000-4000-8000-0000000000ff\",\n \"payload\": {\n \"bundleId\": \"3f2504e0-4f89-41d3-9a0c-0305e82c3301\",\n \"downloaded\": true\n }\n}\n";
let doc: crate::TrustTask<super::Response> =
serde_json::from_str(JSON).expect("deserialize response example");
let rendered = serde_json::to_value(&doc).expect("re-serialize");
let expected: serde_json::Value = serde_json::from_str(JSON).expect("re-parse expected");
assert_eq!(rendered, expected, "response example failed round-trip");
}
/// Each fixture in `payload.invalid-examples.json` MUST be
/// rejected by at least one of: serde deserialization, or
/// JSON-Schema validation under the `validate` feature. The
/// fixture file documents the producer-side bug class that
/// each payload exemplifies; this generated test pins it.
#[cfg(feature = "validate")]
#[test]
fn rejects_invalid_examples() {
use crate::validate::ValidatedPayload;
let fixtures: &[(&str, &str)] = &[
(
"Missing `bundleId`. There is no current bundle and no most-recent default — an absent handle must never resolve to one.",
"{}",
),
(
"`bundleId` that is not a UUID. The pattern is what keeps a guessed or crafted handle from reaching the recipient's store as a lookup key.",
"{\n \"bundleId\": \"latest\"\n}",
),
(
"Bare/unnamespaced ext key — SPEC §4.5.1 requires every immediate child of ext to be reverse-DNS namespaced.",
"{\n \"bundleId\": \"3f2504e0-4f89-41d3-9a0c-0305e82c3301\",\n \"ext\": {\n \"bare-key\": {\n \"anything\": \"here\"\n }\n }\n}",
),
(
"Unknown top-level member — additionalProperties: false catches `downloaded`. Whether the bytes were fetched is the recipient's observation; a producer asserting it would be asserting a fact only the recipient holds.",
"{\n \"bundleId\": \"3f2504e0-4f89-41d3-9a0c-0305e82c3301\",\n \"downloaded\": true\n}",
),
];
for (i, (note, raw)) in fixtures.iter().enumerate() {
let value: serde_json::Value = match serde_json::from_str(raw) {
Ok(v) => v,
Err(_) => continue,
};
let serde_ok = serde_json::from_value::<super::Payload>(value.clone()).is_ok();
let schema_ok = super::Payload::validate_value(&value).is_ok();
assert!(
!(serde_ok && schema_ok),
"invalid-example #{} ({:?}) was accepted by both serde and JSON Schema; \
the fixture's stated failure class is no longer caught:\n{}",
i + 1,
note,
raw
);
}
}
}