//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `acl/list`. Version: `0.1`.
#[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())
}
}
}
///`AclEntry`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "AclEntry",
/// "type": "object",
/// "required": [
/// "role",
/// "subject"
/// ],
/// "properties": {
/// "allowedKeys": {
/// "description": "Key identifiers this subject may invoke the maintainer's signing oracle on. INTERSECTS WITH `scopes` — it can only narrow, never widen: a key named here that lies outside the entry's scopes remains unreachable, exactly as if it were not named. ABSENT means every key within the entry's scopes (the behaviour of entries that pre-date this member); explicit `null` is equivalent to absent, and producers SHOULD omit the member instead. PRESENT-BUT-EMPTY means authorized on NO keys — the opposite of absent, and deliberately so: emptiness is never a wildcard (CONVENTIONS.md §5). A consumer MUST preserve and enforce the absent-vs-empty distinction end to end; collapsing the two (e.g. by testing emptiness alone) re-creates the empty-means-unrestricted class of privilege-escalation defect this family's conventions exist to prevent.",
/// "type": [
/// "array",
/// "null"
/// ],
/// "items": {
/// "type": "string"
/// }
/// },
/// "approve": {
/// "description": "Approve-authority: what this subject may **confer on others** by ratifying an approval, as distinct from `scopes`, which is what it may **exercise itself**. The two axes are independent, and that independence is the point — it is what lets a maintainer configure a least-privilege approver who can authorize an operation in a scope it has no authority to perform.\n\nOMISSION MEANS NOTHING IS CONFERRED. An absent `approve`, an absent `all`, and an empty `scopes` are all equivalent to \"this subject may ratify nothing\". A consumer that does not implement this member therefore confers less than the producer intended rather than more, which is the direction a missed member has to fail in.\n\nA subject with approve-authority is NOT thereby authorized to act. Consumers MUST resolve the two axes separately: reading `approve` to answer \"may this party ratify X\" and `scopes` to answer \"may this party do X\". Collapsing them grants an approver the ability to perform what it was only meant to sign off on.",
/// "type": "object",
/// "properties": {
/// "all": {
/// "description": "The subject may confer ANY scope. Takes precedence over `scopes`, which a consumer MUST ignore when this is true. Absent or false → only the scopes listed below, if any.",
/// "default": false,
/// "type": "boolean"
/// },
/// "scopes": {
/// "description": "Opaque scope identifiers this subject may confer, drawn from the same vocabulary as the entry's own `scopes`. Where a maintainer's scopes are hierarchical, conferring a scope confers its descendants — the same containment rule the maintainer already applies to `scopes`, so the two axes cannot disagree about what a scope means. An empty array confers nothing; it is not a wildcard.",
/// "type": "array",
/// "items": {
/// "type": "string"
/// }
/// }
/// },
/// "additionalProperties": false
/// },
/// "createdAt": {
/// "type": "string",
/// "format": "date-time"
/// },
/// "createdBy": {
/// "description": "VID of the party that originally added this entry.",
/// "type": "string"
/// },
/// "expiresAt": {
/// "description": "Optional time after which the entry is no longer effective.",
/// "type": "string",
/// "format": "date-time"
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1. Reverse-DNS-namespaced; consumers MUST ignore unrecognized namespaces.",
/// "$ref": "#/definitions/Ext"
/// },
/// "label": {
/// "description": "Optional human-readable label.",
/// "type": "string"
/// },
/// "role": {
/// "description": "Opaque role identifier interpreted by the ACL maintainer.",
/// "type": "string"
/// },
/// "scopes": {
/// "description": "Opaque scope identifiers (e.g. contexts, domains, resource prefixes).",
/// "type": "array",
/// "items": {
/// "type": "string"
/// }
/// },
/// "stepUp": {
/// "description": "Per-entry authentication step-up configuration, consumed by the ACL maintainer when it gates an operation behind a step-up (see auth/step-up/policy/0.1). ADDITIVE-ONLY: a per-entry setting MAY raise the assurance required of this subject above the maintainer's system-wide floor, but MUST NOT lower it. The maintainer resolves the effective requirement as the strictest of (system floor, this entry).",
/// "type": "object",
/// "properties": {
/// "approver": {
/// "description": "VID authorized to ratify step-up for this subject — the `recipient` the maintainer addresses an auth/step-up/approve-request to (e.g. the holder's mobile authenticator or browser companion). Absent → the subject is its own approver (mode `self`) when it holds a usable authenticator; if neither an `approver` nor a self authenticator exists, no step-up method is available for this subject and the maintainer's fail-closed rule applies.",
/// "type": "string"
/// },
/// "require": {
/// "description": "Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).",
/// "type": "string",
/// "enum": [
/// "self",
/// "delegated"
/// ]
/// }
/// },
/// "additionalProperties": false
/// },
/// "subject": {
/// "description": "VID of the party in the ACL. Compared by exact string equality (SPEC.md §4.8); producers SHOULD emit canonical form.",
/// "type": "string"
/// },
/// "updatedAt": {
/// "type": "string",
/// "format": "date-time"
/// },
/// "updatedBy": {
/// "description": "VID of the party that last modified this entry.",
/// "type": "string"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct AclEntry {
///Key identifiers this subject may invoke the maintainer's signing oracle on. INTERSECTS WITH `scopes` — it can only narrow, never widen: a key named here that lies outside the entry's scopes remains unreachable, exactly as if it were not named. ABSENT means every key within the entry's scopes (the behaviour of entries that pre-date this member); explicit `null` is equivalent to absent, and producers SHOULD omit the member instead. PRESENT-BUT-EMPTY means authorized on NO keys — the opposite of absent, and deliberately so: emptiness is never a wildcard (CONVENTIONS.md §5). A consumer MUST preserve and enforce the absent-vs-empty distinction end to end; collapsing the two (e.g. by testing emptiness alone) re-creates the empty-means-unrestricted class of privilege-escalation defect this family's conventions exist to prevent.
#[serde(
rename = "allowedKeys",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub allowed_keys: ::std::option::Option<::std::vec::Vec<::std::string::String>>,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub approve: ::std::option::Option<AclEntryApprove>,
#[serde(
rename = "createdAt",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub created_at: ::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
///VID of the party that originally added this entry.
#[serde(
rename = "createdBy",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub created_by: ::std::option::Option<::std::string::String>,
///Optional time after which the entry is no longer effective.
#[serde(
rename = "expiresAt",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub expires_at: ::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
///Ecosystem-defined extension members per SPEC.md §4.5.1. Reverse-DNS-namespaced; consumers MUST ignore unrecognized namespaces.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///Optional human-readable label.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub label: ::std::option::Option<::std::string::String>,
///Opaque role identifier interpreted by the ACL maintainer.
pub role: ::std::string::String,
///Opaque scope identifiers (e.g. contexts, domains, resource prefixes).
#[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")]
pub scopes: ::std::vec::Vec<::std::string::String>,
#[serde(
rename = "stepUp",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub step_up: ::std::option::Option<AclEntryStepUp>,
///VID of the party in the ACL. Compared by exact string equality (SPEC.md §4.8); producers SHOULD emit canonical form.
pub subject: ::std::string::String,
#[serde(
rename = "updatedAt",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub updated_at: ::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
///VID of the party that last modified this entry.
#[serde(
rename = "updatedBy",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub updated_by: ::std::option::Option<::std::string::String>,
}
/**Approve-authority: what this subject may **confer on others** by ratifying an approval, as distinct from `scopes`, which is what it may **exercise itself**. The two axes are independent, and that independence is the point — it is what lets a maintainer configure a least-privilege approver who can authorize an operation in a scope it has no authority to perform.
OMISSION MEANS NOTHING IS CONFERRED. An absent `approve`, an absent `all`, and an empty `scopes` are all equivalent to "this subject may ratify nothing". A consumer that does not implement this member therefore confers less than the producer intended rather than more, which is the direction a missed member has to fail in.
A subject with approve-authority is NOT thereby authorized to act. Consumers MUST resolve the two axes separately: reading `approve` to answer "may this party ratify X" and `scopes` to answer "may this party do X". Collapsing them grants an approver the ability to perform what it was only meant to sign off on.*/
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Approve-authority: what this subject may **confer on others** by ratifying an approval, as distinct from `scopes`, which is what it may **exercise itself**. The two axes are independent, and that independence is the point — it is what lets a maintainer configure a least-privilege approver who can authorize an operation in a scope it has no authority to perform.\n\nOMISSION MEANS NOTHING IS CONFERRED. An absent `approve`, an absent `all`, and an empty `scopes` are all equivalent to \"this subject may ratify nothing\". A consumer that does not implement this member therefore confers less than the producer intended rather than more, which is the direction a missed member has to fail in.\n\nA subject with approve-authority is NOT thereby authorized to act. Consumers MUST resolve the two axes separately: reading `approve` to answer \"may this party ratify X\" and `scopes` to answer \"may this party do X\". Collapsing them grants an approver the ability to perform what it was only meant to sign off on.",
/// "type": "object",
/// "properties": {
/// "all": {
/// "description": "The subject may confer ANY scope. Takes precedence over `scopes`, which a consumer MUST ignore when this is true. Absent or false → only the scopes listed below, if any.",
/// "default": false,
/// "type": "boolean"
/// },
/// "scopes": {
/// "description": "Opaque scope identifiers this subject may confer, drawn from the same vocabulary as the entry's own `scopes`. Where a maintainer's scopes are hierarchical, conferring a scope confers its descendants — the same containment rule the maintainer already applies to `scopes`, so the two axes cannot disagree about what a scope means. An empty array confers nothing; it is not a wildcard.",
/// "type": "array",
/// "items": {
/// "type": "string"
/// }
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct AclEntryApprove {
///The subject may confer ANY scope. Takes precedence over `scopes`, which a consumer MUST ignore when this is true. Absent or false → only the scopes listed below, if any.
#[serde(default)]
pub all: bool,
///Opaque scope identifiers this subject may confer, drawn from the same vocabulary as the entry's own `scopes`. Where a maintainer's scopes are hierarchical, conferring a scope confers its descendants — the same containment rule the maintainer already applies to `scopes`, so the two axes cannot disagree about what a scope means. An empty array confers nothing; it is not a wildcard.
#[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")]
pub scopes: ::std::vec::Vec<::std::string::String>,
}
impl ::std::default::Default for AclEntryApprove {
fn default() -> Self {
Self {
all: Default::default(),
scopes: Default::default(),
}
}
}
///Per-entry authentication step-up configuration, consumed by the ACL maintainer when it gates an operation behind a step-up (see auth/step-up/policy/0.1). ADDITIVE-ONLY: a per-entry setting MAY raise the assurance required of this subject above the maintainer's system-wide floor, but MUST NOT lower it. The maintainer resolves the effective requirement as the strictest of (system floor, this entry).
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Per-entry authentication step-up configuration, consumed by the ACL maintainer when it gates an operation behind a step-up (see auth/step-up/policy/0.1). ADDITIVE-ONLY: a per-entry setting MAY raise the assurance required of this subject above the maintainer's system-wide floor, but MUST NOT lower it. The maintainer resolves the effective requirement as the strictest of (system floor, this entry).",
/// "type": "object",
/// "properties": {
/// "approver": {
/// "description": "VID authorized to ratify step-up for this subject — the `recipient` the maintainer addresses an auth/step-up/approve-request to (e.g. the holder's mobile authenticator or browser companion). Absent → the subject is its own approver (mode `self`) when it holds a usable authenticator; if neither an `approver` nor a self authenticator exists, no step-up method is available for this subject and the maintainer's fail-closed rule applies.",
/// "type": "string"
/// },
/// "require": {
/// "description": "Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).",
/// "type": "string",
/// "enum": [
/// "self",
/// "delegated"
/// ]
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct AclEntryStepUp {
///VID authorized to ratify step-up for this subject — the `recipient` the maintainer addresses an auth/step-up/approve-request to (e.g. the holder's mobile authenticator or browser companion). Absent → the subject is its own approver (mode `self`) when it holds a usable authenticator; if neither an `approver` nor a self authenticator exists, no step-up method is available for this subject and the maintainer's fail-closed rule applies.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub approver: ::std::option::Option<::std::string::String>,
///Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub require: ::std::option::Option<AclEntryStepUpRequire>,
}
impl ::std::default::Default for AclEntryStepUp {
fn default() -> Self {
Self {
approver: Default::default(),
require: Default::default(),
}
}
}
///Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).",
/// "type": "string",
/// "enum": [
/// "self",
/// "delegated"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum AclEntryStepUpRequire {
#[serde(rename = "self")]
Self_,
#[serde(rename = "delegated")]
Delegated,
}
impl ::std::fmt::Display for AclEntryStepUpRequire {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Self_ => f.write_str("self"),
Self::Delegated => f.write_str("delegated"),
}
}
}
impl ::std::str::FromStr for AclEntryStepUpRequire {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"self" => Ok(Self::Self_),
"delegated" => Ok(Self::Delegated),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for AclEntryStepUpRequire {
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 AclEntryStepUpRequire {
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 AclEntryStepUpRequire {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///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())
})
}
}
///`Payload`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/acl/list/0.1",
/// "title": "Payload",
/// "type": "object",
/// "properties": {
/// "cursor": {
/// "description": "Opaque continuation token returned by the maintainer in a previous response.",
/// "type": "string"
/// },
/// "direction": {
/// "description": "How to read `scope` where the maintainer's scopes are hierarchical: `acting-in` (the default when omitted) returns entries that may act IN the scope — scoped to it or to an ancestor; `subtree` returns entries holding a grant at or BENEATH it; `any` is the union. A consumer without hierarchical scopes MAY treat all three alike. See the specification for why the distinction matters to a revocation sweep.",
/// "type": "string",
/// "enum": [
/// "acting-in",
/// "subtree",
/// "any"
/// ]
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1.",
/// "$ref": "#/definitions/Ext"
/// },
/// "pageSize": {
/// "description": "Maximum number of entries to return. Maintainer-defined default and ceiling.",
/// "type": "integer",
/// "maximum": 1000.0,
/// "minimum": 1.0
/// },
/// "role": {
/// "description": "Optional filter — only entries with this role are returned.",
/// "type": "string",
/// "minLength": 1
/// },
/// "scope": {
/// "description": "Optional filter — only entries whose scopes include this string are returned.",
/// "type": "string",
/// "minLength": 1
/// },
/// "subjectPrefix": {
/// "description": "Optional filter — only entries whose subject VID starts with this prefix are returned.",
/// "type": "string",
/// "minLength": 1
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct Payload {
///Opaque continuation token returned by the maintainer in a previous response.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub cursor: ::std::option::Option<::std::string::String>,
///How to read `scope` where the maintainer's scopes are hierarchical: `acting-in` (the default when omitted) returns entries that may act IN the scope — scoped to it or to an ancestor; `subtree` returns entries holding a grant at or BENEATH it; `any` is the union. A consumer without hierarchical scopes MAY treat all three alike. See the specification for why the distinction matters to a revocation sweep.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub direction: ::std::option::Option<PayloadDirection>,
///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>,
///Maximum number of entries to return. Maintainer-defined default and ceiling.
#[serde(
rename = "pageSize",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub page_size: ::std::option::Option<::std::num::NonZeroU64>,
///Optional filter — only entries with this role are returned.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub role: ::std::option::Option<PayloadRole>,
///Optional filter — only entries whose scopes include this string are returned.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub scope: ::std::option::Option<PayloadScope>,
///Optional filter — only entries whose subject VID starts with this prefix are returned.
#[serde(
rename = "subjectPrefix",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub subject_prefix: ::std::option::Option<PayloadSubjectPrefix>,
}
impl ::std::default::Default for Payload {
fn default() -> Self {
Self {
cursor: Default::default(),
direction: Default::default(),
ext: Default::default(),
page_size: Default::default(),
role: Default::default(),
scope: Default::default(),
subject_prefix: Default::default(),
}
}
}
///How to read `scope` where the maintainer's scopes are hierarchical: `acting-in` (the default when omitted) returns entries that may act IN the scope — scoped to it or to an ancestor; `subtree` returns entries holding a grant at or BENEATH it; `any` is the union. A consumer without hierarchical scopes MAY treat all three alike. See the specification for why the distinction matters to a revocation sweep.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "How to read `scope` where the maintainer's scopes are hierarchical: `acting-in` (the default when omitted) returns entries that may act IN the scope — scoped to it or to an ancestor; `subtree` returns entries holding a grant at or BENEATH it; `any` is the union. A consumer without hierarchical scopes MAY treat all three alike. See the specification for why the distinction matters to a revocation sweep.",
/// "type": "string",
/// "enum": [
/// "acting-in",
/// "subtree",
/// "any"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum PayloadDirection {
#[serde(rename = "acting-in")]
ActingIn,
#[serde(rename = "subtree")]
Subtree,
#[serde(rename = "any")]
Any,
}
impl ::std::fmt::Display for PayloadDirection {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::ActingIn => f.write_str("acting-in"),
Self::Subtree => f.write_str("subtree"),
Self::Any => f.write_str("any"),
}
}
}
impl ::std::str::FromStr for PayloadDirection {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"acting-in" => Ok(Self::ActingIn),
"subtree" => Ok(Self::Subtree),
"any" => Ok(Self::Any),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for PayloadDirection {
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 PayloadDirection {
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 PayloadDirection {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///Optional filter — only entries with this role are returned.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Optional filter — only entries with this role are returned.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadRole(::std::string::String);
impl ::std::ops::Deref for PayloadRole {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadRole> for ::std::string::String {
fn from(value: PayloadRole) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadRole {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadRole {
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 PayloadRole {
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 PayloadRole {
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 PayloadRole {
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())
})
}
}
///Optional filter — only entries whose scopes include this string are returned.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Optional filter — only entries whose scopes include this string are returned.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadScope(::std::string::String);
impl ::std::ops::Deref for PayloadScope {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadScope> for ::std::string::String {
fn from(value: PayloadScope) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadScope {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadScope {
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 PayloadScope {
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 PayloadScope {
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 PayloadScope {
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())
})
}
}
///Optional filter — only entries whose subject VID starts with this prefix are returned.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Optional filter — only entries whose subject VID starts with this prefix are returned.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadSubjectPrefix(::std::string::String);
impl ::std::ops::Deref for PayloadSubjectPrefix {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadSubjectPrefix> for ::std::string::String {
fn from(value: PayloadSubjectPrefix) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadSubjectPrefix {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadSubjectPrefix {
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 PayloadSubjectPrefix {
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 PayloadSubjectPrefix {
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 PayloadSubjectPrefix {
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())
})
}
}
///The success response to an acl/list request. Carried in a Trust Task document whose type is https://trusttasks.org/spec/acl/list/0.1#response.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "description": "The success response to an acl/list request. Carried in a Trust Task document whose type is https://trusttasks.org/spec/acl/list/0.1#response.",
/// "type": "object",
/// "required": [
/// "entries",
/// "truncated"
/// ],
/// "properties": {
/// "cursor": {
/// "description": "Opaque continuation token to fetch the next page. Present only when `truncated` is true AND the maintainer supports pagination from this point. Consumers MUST treat the cursor as opaque and re-send it verbatim.",
/// "type": "string"
/// },
/// "entries": {
/// "description": "Matching AclEntry items, in maintainer-defined order. May be empty.",
/// "type": "array",
/// "items": {
/// "$ref": "#/definitions/AclEntry"
/// }
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1.",
/// "$ref": "#/definitions/Ext"
/// },
/// "redactedFields": {
/// "description": "Names of AclEntry fields the maintainer redacted from every returned entry (for example, ['label'] or ['ext.vnd.example.audit']).",
/// "type": "array",
/// "items": {
/// "type": "string"
/// }
/// },
/// "truncated": {
/// "description": "true when more matching entries exist beyond `entries`; false when this response is the complete result. Independent of `cursor`: a maintainer MAY truncate without supporting pagination, in which case `truncated` is true and `cursor` is absent.",
/// "type": "boolean"
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "response"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct Response {
///Opaque continuation token to fetch the next page. Present only when `truncated` is true AND the maintainer supports pagination from this point. Consumers MUST treat the cursor as opaque and re-send it verbatim.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub cursor: ::std::option::Option<::std::string::String>,
///Matching AclEntry items, in maintainer-defined order. May be empty.
pub entries: ::std::vec::Vec<AclEntry>,
///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>,
///Names of AclEntry fields the maintainer redacted from every returned entry (for example, ['label'] or ['ext.vnd.example.audit']).
#[serde(
rename = "redactedFields",
default,
skip_serializing_if = "::std::vec::Vec::is_empty"
)]
pub redacted_fields: ::std::vec::Vec<::std::string::String>,
///true when more matching entries exist beyond `entries`; false when this response is the complete result. Independent of `cursor`: a maintainer MAY truncate without supporting pagination, in which case `truncated` is true and `cursor` is absent.
pub truncated: bool,
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/acl/list/0.1";
const IS_RECIPIENT_REQUIRED: bool = true;
}
impl crate::Payload for Response {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/acl/list/0.1#response";
const IS_RECIPIENT_REQUIRED: bool = true;
}
#[cfg(feature = "validate")]
impl crate::validate::ValidatedPayload for Payload {
const SCHEMA_JSON: &'static str = "{\n \"$defs\": {\n \"AclEntry\": {\n \"additionalProperties\": false,\n \"properties\": {\n \"allowedKeys\": {\n \"description\": \"Key identifiers this subject may invoke the maintainer's signing oracle on. INTERSECTS WITH `scopes` — it can only narrow, never widen: a key named here that lies outside the entry's scopes remains unreachable, exactly as if it were not named. ABSENT means every key within the entry's scopes (the behaviour of entries that pre-date this member); explicit `null` is equivalent to absent, and producers SHOULD omit the member instead. PRESENT-BUT-EMPTY means authorized on NO keys — the opposite of absent, and deliberately so: emptiness is never a wildcard (CONVENTIONS.md §5). A consumer MUST preserve and enforce the absent-vs-empty distinction end to end; collapsing the two (e.g. by testing emptiness alone) re-creates the empty-means-unrestricted class of privilege-escalation defect this family's conventions exist to prevent.\",\n \"items\": {\n \"type\": \"string\"\n },\n \"type\": [\n \"array\",\n \"null\"\n ]\n },\n \"approve\": {\n \"additionalProperties\": false,\n \"description\": \"Approve-authority: what this subject may **confer on others** by ratifying an approval, as distinct from `scopes`, which is what it may **exercise itself**. The two axes are independent, and that independence is the point — it is what lets a maintainer configure a least-privilege approver who can authorize an operation in a scope it has no authority to perform.\\n\\nOMISSION MEANS NOTHING IS CONFERRED. An absent `approve`, an absent `all`, and an empty `scopes` are all equivalent to \\\"this subject may ratify nothing\\\". A consumer that does not implement this member therefore confers less than the producer intended rather than more, which is the direction a missed member has to fail in.\\n\\nA subject with approve-authority is NOT thereby authorized to act. Consumers MUST resolve the two axes separately: reading `approve` to answer \\\"may this party ratify X\\\" and `scopes` to answer \\\"may this party do X\\\". Collapsing them grants an approver the ability to perform what it was only meant to sign off on.\",\n \"properties\": {\n \"all\": {\n \"default\": false,\n \"description\": \"The subject may confer ANY scope. Takes precedence over `scopes`, which a consumer MUST ignore when this is true. Absent or false → only the scopes listed below, if any.\",\n \"type\": \"boolean\"\n },\n \"scopes\": {\n \"description\": \"Opaque scope identifiers this subject may confer, drawn from the same vocabulary as the entry's own `scopes`. Where a maintainer's scopes are hierarchical, conferring a scope confers its descendants — the same containment rule the maintainer already applies to `scopes`, so the two axes cannot disagree about what a scope means. An empty array confers nothing; it is not a wildcard.\",\n \"items\": {\n \"type\": \"string\"\n },\n \"type\": \"array\"\n }\n },\n \"type\": \"object\"\n },\n \"createdAt\": {\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"createdBy\": {\n \"description\": \"VID of the party that originally added this entry.\",\n \"type\": \"string\"\n },\n \"expiresAt\": {\n \"description\": \"Optional time after which the entry is no longer effective.\",\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1. Reverse-DNS-namespaced; consumers MUST ignore unrecognized namespaces.\"\n },\n \"label\": {\n \"description\": \"Optional human-readable label.\",\n \"type\": \"string\"\n },\n \"role\": {\n \"description\": \"Opaque role identifier interpreted by the ACL maintainer.\",\n \"type\": \"string\"\n },\n \"scopes\": {\n \"description\": \"Opaque scope identifiers (e.g. contexts, domains, resource prefixes).\",\n \"items\": {\n \"type\": \"string\"\n },\n \"type\": \"array\"\n },\n \"stepUp\": {\n \"additionalProperties\": false,\n \"description\": \"Per-entry authentication step-up configuration, consumed by the ACL maintainer when it gates an operation behind a step-up (see auth/step-up/policy/0.1). ADDITIVE-ONLY: a per-entry setting MAY raise the assurance required of this subject above the maintainer's system-wide floor, but MUST NOT lower it. The maintainer resolves the effective requirement as the strictest of (system floor, this entry).\",\n \"properties\": {\n \"approver\": {\n \"description\": \"VID authorized to ratify step-up for this subject — the `recipient` the maintainer addresses an auth/step-up/approve-request to (e.g. the holder's mobile authenticator or browser companion). Absent → the subject is its own approver (mode `self`) when it holds a usable authenticator; if neither an `approver` nor a self authenticator exists, no step-up method is available for this subject and the maintainer's fail-closed rule applies.\",\n \"type\": \"string\"\n },\n \"require\": {\n \"description\": \"Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).\",\n \"enum\": [\n \"self\",\n \"delegated\"\n ],\n \"type\": \"string\"\n }\n },\n \"type\": \"object\"\n },\n \"subject\": {\n \"description\": \"VID of the party in the ACL. Compared by exact string equality (SPEC.md §4.8); producers SHOULD emit canonical form.\",\n \"type\": \"string\"\n },\n \"updatedAt\": {\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"updatedBy\": {\n \"description\": \"VID of the party that last modified this entry.\",\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"subject\",\n \"role\"\n ],\n \"title\": \"AclEntry\",\n \"type\": \"object\"\n },\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 \"description\": \"The success response to an acl/list request. Carried in a Trust Task document whose type is https://trusttasks.org/spec/acl/list/0.1#response.\",\n \"properties\": {\n \"cursor\": {\n \"description\": \"Opaque continuation token to fetch the next page. Present only when `truncated` is true AND the maintainer supports pagination from this point. Consumers MUST treat the cursor as opaque and re-send it verbatim.\",\n \"type\": \"string\"\n },\n \"entries\": {\n \"description\": \"Matching AclEntry items, in maintainer-defined order. May be empty.\",\n \"items\": {\n \"$ref\": \"#/$defs/AclEntry\"\n },\n \"type\": \"array\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n },\n \"redactedFields\": {\n \"description\": \"Names of AclEntry fields the maintainer redacted from every returned entry (for example, ['label'] or ['ext.vnd.example.audit']).\",\n \"items\": {\n \"type\": \"string\"\n },\n \"type\": \"array\"\n },\n \"truncated\": {\n \"description\": \"true when more matching entries exist beyond `entries`; false when this response is the complete result. Independent of `cursor`: a maintainer MAY truncate without supporting pagination, in which case `truncated` is true and `cursor` is absent.\",\n \"type\": \"boolean\"\n }\n },\n \"required\": [\n \"entries\",\n \"truncated\"\n ],\n \"title\": \"ACL List — response payload\",\n \"type\": \"object\"\n }\n },\n \"$id\": \"https://trusttasks.org/spec/acl/list/0.1\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"properties\": {\n \"cursor\": {\n \"description\": \"Opaque continuation token returned by the maintainer in a previous response.\",\n \"type\": \"string\"\n },\n \"direction\": {\n \"description\": \"How to read `scope` where the maintainer's scopes are hierarchical: `acting-in` (the default when omitted) returns entries that may act IN the scope — scoped to it or to an ancestor; `subtree` returns entries holding a grant at or BENEATH it; `any` is the union. A consumer without hierarchical scopes MAY treat all three alike. See the specification for why the distinction matters to a revocation sweep.\",\n \"enum\": [\n \"acting-in\",\n \"subtree\",\n \"any\"\n ],\n \"type\": \"string\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n },\n \"pageSize\": {\n \"description\": \"Maximum number of entries to return. Maintainer-defined default and ceiling.\",\n \"maximum\": 1000,\n \"minimum\": 1,\n \"type\": \"integer\"\n },\n \"role\": {\n \"description\": \"Optional filter — only entries with this role are returned.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"scope\": {\n \"description\": \"Optional filter — only entries whose scopes include this string are returned.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"subjectPrefix\": {\n \"description\": \"Optional filter — only entries whose subject VID starts with this prefix are returned.\",\n \"minLength\": 1,\n \"type\": \"string\"\n }\n },\n \"title\": \"ACL List — payload\",\n \"type\": \"object\"\n}\n";
}
#[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\": \"2e2a1c44-7b81-4d3e-9b51-7a3c89e3d1f2\",\n \"type\": \"https://trusttasks.org/spec/acl/list/0.1\",\n \"issuer\": \"did:web:admin.example\",\n \"recipient\": \"did:web:maintainer.example\",\n \"issuedAt\": \"2026-06-15T10:00:00Z\",\n \"payload\": {}\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 request_example_2() {
const JSON: &str = "{\n \"id\": \"5b3c5e2a-1b81-4d3e-9b51-7a3c89e3d1f2\",\n \"type\": \"https://trusttasks.org/spec/acl/list/0.1\",\n \"issuer\": \"did:web:auditor.example\",\n \"recipient\": \"did:web:maintainer.example\",\n \"issuedAt\": \"2026-06-15T10:05:00Z\",\n \"payload\": {\n \"role\": \"admin\",\n \"pageSize\": 50\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 request_example_3() {
const JSON: &str = "{\n \"id\": \"7e2c5e2a-1b81-4d3e-9b51-7a3c89e3d1f2\",\n \"type\": \"https://trusttasks.org/spec/acl/list/0.1\",\n \"issuer\": \"did:web:auditor.example\",\n \"recipient\": \"did:web:maintainer.example\",\n \"issuedAt\": \"2026-06-15T10:06:00Z\",\n \"payload\": {\n \"role\": \"admin\",\n \"pageSize\": 50,\n \"cursor\": \"eyJvZmZzZXQiOjUwfQ\"\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\": \"6c3c5e2a-1b81-4d3e-9b51-7a3c89e3d1f3\",\n \"type\": \"https://trusttasks.org/spec/acl/list/0.1#response\",\n \"threadId\": \"5b3c5e2a-1b81-4d3e-9b51-7a3c89e3d1f2\",\n \"issuer\": \"did:web:maintainer.example\",\n \"recipient\": \"did:web:auditor.example\",\n \"issuedAt\": \"2026-06-15T10:05:01Z\",\n \"payload\": {\n \"entries\": [\n {\n \"subject\": \"did:web:alice.example\",\n \"role\": \"admin\",\n \"label\": \"Alice — primary admin\",\n \"createdAt\": \"2026-05-16T10:00:00Z\",\n \"createdBy\": \"did:web:org.example\"\n },\n {\n \"subject\": \"did:web:carol.example\",\n \"role\": \"admin\",\n \"createdAt\": \"2026-05-18T08:30:00Z\",\n \"createdBy\": \"did:web:alice.example\"\n }\n ],\n \"truncated\": true,\n \"cursor\": \"eyJvZmZzZXQiOjUwfQ\"\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");
}
#[test]
fn response_example_2() {
const JSON: &str = "{\n \"id\": \"7e2c5e2a-1b81-4d3e-9b51-7a3c89e3d1f3\",\n \"type\": \"https://trusttasks.org/spec/acl/list/0.1#response\",\n \"threadId\": \"5b3c5e2a-1b81-4d3e-9b51-7a3c89e3d1f2\",\n \"issuer\": \"did:web:maintainer.example\",\n \"recipient\": \"did:web:auditor.example\",\n \"issuedAt\": \"2026-06-15T10:06:00Z\",\n \"payload\": {\n \"entries\": [\n {\n \"subject\": \"did:web:alice.example\",\n \"role\": \"admin\",\n \"createdAt\": \"2026-05-16T10:00:00Z\",\n \"createdBy\": \"did:web:org.example\"\n }\n ],\n \"truncated\": false,\n \"redactedFields\": [\"label\", \"ext.vnd.example.hr\"]\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)] = &[
(
"Bare/unnamespaced ext key — SPEC §4.5.1 producer rule.",
"{\n \"ext\": {\n \"noNamespace\": {}\n }\n}",
),
(
"pageSize above the spec-imposed maximum of 1000.",
"{\n \"pageSize\": 5000\n}",
),
(
"Unknown top-level filter member — additionalProperties: false catches `unknownFilter`.",
"{\n \"role\": \"admin\",\n \"unknownFilter\": \"value\"\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
);
}
}
}