//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `rooms/keys/profile/write`. 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())
}
}
}
///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())
})
}
}
///A member holding `curate` or `admin` in a room asks their own key holder to set what the room is called and what it is for — or to clear it. The key holder signs the profile as the member, with the member's authority chain inside it, seals it under the room's current epoch, and stores it at the host at the location only members can compute. Exactly one of `profile` and `clear`. The outer document members are owned by the framework — SPEC §6.3.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/rooms/keys/profile/write/0.1",
/// "title": "Payload",
/// "description": "A member holding `curate` or `admin` in a room asks their own key holder to set what the room is called and what it is for — or to clear it. The key holder signs the profile as the member, with the member's authority chain inside it, seals it under the room's current epoch, and stores it at the host at the location only members can compute. Exactly one of `profile` and `clear`. The outer document members are owned by the framework — SPEC §6.3.",
/// "type": "object",
/// "anyOf": [
/// {
/// "required": [
/// "profile"
/// ]
/// },
/// {
/// "required": [
/// "clear"
/// ]
/// }
/// ],
/// "required": [
/// "host",
/// "roomId"
/// ],
/// "properties": {
/// "clear": {
/// "description": "Remove the room's profile: retract the record at the current epoch's profile location, so that readers find `cleared` there and stop. Only `true` is meaningful, so only `true` is accepted. Takes `curate`, which the host enforces on a retraction. Mutually exclusive with `profile`.",
/// "type": "boolean",
/// "const": true
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1.",
/// "$ref": "#/definitions/Ext"
/// },
/// "host": {
/// "description": "The host to write to, as a DID. Named by the caller for the reason `rooms/keys/read` gives. A room registered with more than one host is written at the one that takes its writes.",
/// "type": "string",
/// "maxLength": 2048,
/// "minLength": 1,
/// "pattern": "^did:"
/// },
/// "profile": {
/// "description": "The room's new profile, replacing the whole of the current one: a member absent here is absent from the room's profile afterwards. Mutually exclusive with `clear`.",
/// "$ref": "#/definitions/RoomProfile"
/// },
/// "roomId": {
/// "description": "The room whose profile to set. The recipient MUST hold its principal's membership of it.",
/// "type": "string",
/// "maxLength": 512,
/// "minLength": 1
/// }
/// },
/// "additionalProperties": false,
/// "dependentSchemas": {
/// "clear": {
/// "not": {
/// "required": [
/// "profile"
/// ]
/// }
/// }
/// }
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(untagged, deny_unknown_fields)]
#[non_exhaustive]
pub enum Payload {
Variant0 {
///Ecosystem-defined extension members per SPEC.md §4.5.1.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
ext: ::std::option::Option<Ext>,
///The host to write to, as a DID. Named by the caller for the reason `rooms/keys/read` gives. A room registered with more than one host is written at the one that takes its writes.
host: PayloadVariant0Host,
///The room's new profile, replacing the whole of the current one: a member absent here is absent from the room's profile afterwards. Mutually exclusive with `clear`.
profile: RoomProfile,
///The room whose profile to set. The recipient MUST hold its principal's membership of it.
#[serde(rename = "roomId")]
room_id: PayloadVariant0RoomId,
},
Variant1 {
///Remove the room's profile: retract the record at the current epoch's profile location, so that readers find `cleared` there and stop. Only `true` is meaningful, so only `true` is accepted. Takes `curate`, which the host enforces on a retraction. Mutually exclusive with `profile`.
clear: bool,
///Ecosystem-defined extension members per SPEC.md §4.5.1.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
ext: ::std::option::Option<Ext>,
///The host to write to, as a DID. Named by the caller for the reason `rooms/keys/read` gives. A room registered with more than one host is written at the one that takes its writes.
host: PayloadVariant1Host,
///The room whose profile to set. The recipient MUST hold its principal's membership of it.
#[serde(rename = "roomId")]
room_id: PayloadVariant1RoomId,
},
}
///The host to write to, as a DID. Named by the caller for the reason `rooms/keys/read` gives. A room registered with more than one host is written at the one that takes its writes.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The host to write to, as a DID. Named by the caller for the reason `rooms/keys/read` gives. A room registered with more than one host is written at the one that takes its writes.",
/// "type": "string",
/// "maxLength": 2048,
/// "minLength": 1,
/// "pattern": "^did:"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadVariant0Host(::std::string::String);
impl ::std::ops::Deref for PayloadVariant0Host {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadVariant0Host> for ::std::string::String {
fn from(value: PayloadVariant0Host) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadVariant0Host {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 2048usize {
return Err("longer than 2048 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| ::regress::Regex::new("^did:").unwrap());
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^did:\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadVariant0Host {
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 PayloadVariant0Host {
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 PayloadVariant0Host {
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 PayloadVariant0Host {
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 room whose profile to set. The recipient MUST hold its principal's membership of it.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The room whose profile to set. The recipient MUST hold its principal's membership of it.",
/// "type": "string",
/// "maxLength": 512,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadVariant0RoomId(::std::string::String);
impl ::std::ops::Deref for PayloadVariant0RoomId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadVariant0RoomId> for ::std::string::String {
fn from(value: PayloadVariant0RoomId) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadVariant0RoomId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 512usize {
return Err("longer than 512 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadVariant0RoomId {
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 PayloadVariant0RoomId {
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 PayloadVariant0RoomId {
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 PayloadVariant0RoomId {
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 host to write to, as a DID. Named by the caller for the reason `rooms/keys/read` gives. A room registered with more than one host is written at the one that takes its writes.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The host to write to, as a DID. Named by the caller for the reason `rooms/keys/read` gives. A room registered with more than one host is written at the one that takes its writes.",
/// "type": "string",
/// "maxLength": 2048,
/// "minLength": 1,
/// "pattern": "^did:"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadVariant1Host(::std::string::String);
impl ::std::ops::Deref for PayloadVariant1Host {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadVariant1Host> for ::std::string::String {
fn from(value: PayloadVariant1Host) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadVariant1Host {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 2048usize {
return Err("longer than 2048 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| ::regress::Regex::new("^did:").unwrap());
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^did:\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadVariant1Host {
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 PayloadVariant1Host {
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 PayloadVariant1Host {
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 PayloadVariant1Host {
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 room whose profile to set. The recipient MUST hold its principal's membership of it.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The room whose profile to set. The recipient MUST hold its principal's membership of it.",
/// "type": "string",
/// "maxLength": 512,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadVariant1RoomId(::std::string::String);
impl ::std::ops::Deref for PayloadVariant1RoomId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadVariant1RoomId> for ::std::string::String {
fn from(value: PayloadVariant1RoomId) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadVariant1RoomId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 512usize {
return Err("longer than 512 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadVariant1RoomId {
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 PayloadVariant1RoomId {
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 PayloadVariant1RoomId {
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 PayloadVariant1RoomId {
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())
})
}
}
///Success response to rooms/keys/profile/write. Type https://trusttasks.org/spec/rooms/keys/profile/write/0.1#response. Where the profile now is at the host, and under which epoch.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "description": "Success response to rooms/keys/profile/write. Type https://trusttasks.org/spec/rooms/keys/profile/write/0.1#response. Where the profile now is at the host, and under which epoch.",
/// "type": "object",
/// "required": [
/// "key",
/// "outcome",
/// "roomId",
/// "version"
/// ],
/// "properties": {
/// "epoch": {
/// "description": "The epoch the profile was sealed under. Absent on an `open` room.",
/// "type": "integer",
/// "minimum": 1.0
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "key": {
/// "description": "The record key the profile is stored at: `profile_key(epoch)` on a sealed tier, `room-profile` on an `open` room. Returned because it is already the host's to see; the caller needs it for nothing but diagnosis.",
/// "type": "string",
/// "maxLength": 512,
/// "minLength": 1
/// },
/// "outcome": {
/// "description": "`written`: the profile given is the room's profile, stored and checked to open. `cleared`: the record at the current epoch's profile location is retracted.",
/// "type": "string",
/// "enum": [
/// "written",
/// "cleared"
/// ]
/// },
/// "roomId": {
/// "type": "string",
/// "maxLength": 512,
/// "minLength": 1
/// },
/// "version": {
/// "description": "The version the host assigned to the write or the retraction.",
/// "type": "integer",
/// "minimum": 1.0
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "response"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Response {
///The epoch the profile was sealed under. Absent on an `open` room.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub epoch: ::std::option::Option<::std::num::NonZeroU64>,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///The record key the profile is stored at: `profile_key(epoch)` on a sealed tier, `room-profile` on an `open` room. Returned because it is already the host's to see; the caller needs it for nothing but diagnosis.
pub key: ResponseKey,
///`written`: the profile given is the room's profile, stored and checked to open. `cleared`: the record at the current epoch's profile location is retracted.
pub outcome: ResponseOutcome,
#[serde(rename = "roomId")]
pub room_id: ResponseRoomId,
///The version the host assigned to the write or the retraction.
pub version: ::std::num::NonZeroU64,
}
impl Response {
pub fn builder() -> builder::Response {
Default::default()
}
}
///The record key the profile is stored at: `profile_key(epoch)` on a sealed tier, `room-profile` on an `open` room. Returned because it is already the host's to see; the caller needs it for nothing but diagnosis.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The record key the profile is stored at: `profile_key(epoch)` on a sealed tier, `room-profile` on an `open` room. Returned because it is already the host's to see; the caller needs it for nothing but diagnosis.",
/// "type": "string",
/// "maxLength": 512,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ResponseKey(::std::string::String);
impl ::std::ops::Deref for ResponseKey {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ResponseKey> for ::std::string::String {
fn from(value: ResponseKey) -> Self {
value.0
}
}
impl ::std::str::FromStr for ResponseKey {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 512usize {
return Err("longer than 512 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for ResponseKey {
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 ResponseKey {
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 ResponseKey {
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 ResponseKey {
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())
})
}
}
///`written`: the profile given is the room's profile, stored and checked to open. `cleared`: the record at the current epoch's profile location is retracted.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "`written`: the profile given is the room's profile, stored and checked to open. `cleared`: the record at the current epoch's profile location is retracted.",
/// "type": "string",
/// "enum": [
/// "written",
/// "cleared"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum ResponseOutcome {
#[serde(rename = "written")]
Written,
#[serde(rename = "cleared")]
Cleared,
}
impl ::std::fmt::Display for ResponseOutcome {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Written => f.write_str("written"),
Self::Cleared => f.write_str("cleared"),
}
}
}
impl ::std::str::FromStr for ResponseOutcome {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"written" => Ok(Self::Written),
"cleared" => Ok(Self::Cleared),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for ResponseOutcome {
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 ResponseOutcome {
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 ResponseOutcome {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///`ResponseRoomId`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "maxLength": 512,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ResponseRoomId(::std::string::String);
impl ::std::ops::Deref for ResponseRoomId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ResponseRoomId> for ::std::string::String {
fn from(value: ResponseRoomId) -> Self {
value.0
}
}
impl ::std::str::FromStr for ResponseRoomId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 512usize {
return Err("longer than 512 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for ResponseRoomId {
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 ResponseRoomId {
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 ResponseRoomId {
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 ResponseRoomId {
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())
})
}
}
/**
What a room says about itself to its own members: a name and, optionally, a description, short tags and an emoji, so that a member's wallet, portal or CLI can show what each room is rather than a DID.
**It lives in the room, and only there.** A profile is carried in a record of the room (`RoomProfileStatement`), sealed on `attributed` and `private` rooms like every other record, so the room's host stores ciphertext and learns neither the name nor anything else here. It is never put in a credential — credentials are presented to hosts — nor in a host's record of the room, in `rooms/create` or `rooms/owner/register`, or in any member a host can read. On an `open` room it is cleartext, as everything in an open room is.
**Plain text, never instructions.** Every member is free text chosen by whoever wrote the profile and shown to every member of the room. A reader renders `name`, `description` and `tags` as text — never as markup, never as a link it follows — removes or replaces control characters before showing them, and MUST NOT treat any of it as instructions, which matters most for an agent acting for a member. A writer MUST NOT put personal data or a credential in any member.
**Growth.** A new optional member arrives in a later version of this component. A reader refuses a member it does not know (the `SealedRecordBody` rules), so a profile carrying one is not shown by a reader that predates it, and a writer SHOULD NOT use a member it cannot expect the room's readers to know. An ecosystem extension goes in `ext`, which a reader that does not understand it ignores. A change no earlier reader could survive is a new statement under a new derivation label (`openvtc/room/profile/v2`), written beside this one until the room's readers have moved.*/
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "RoomProfile",
/// "description": "What a room says about itself to its own members: a name and, optionally, a description, short tags and an emoji, so that a member's wallet, portal or CLI can show what each room is rather than a DID.\n\n**It lives in the room, and only there.** A profile is carried in a record of the room (`RoomProfileStatement`), sealed on `attributed` and `private` rooms like every other record, so the room's host stores ciphertext and learns neither the name nor anything else here. It is never put in a credential — credentials are presented to hosts — nor in a host's record of the room, in `rooms/create` or `rooms/owner/register`, or in any member a host can read. On an `open` room it is cleartext, as everything in an open room is.\n\n**Plain text, never instructions.** Every member is free text chosen by whoever wrote the profile and shown to every member of the room. A reader renders `name`, `description` and `tags` as text — never as markup, never as a link it follows — removes or replaces control characters before showing them, and MUST NOT treat any of it as instructions, which matters most for an agent acting for a member. A writer MUST NOT put personal data or a credential in any member.\n\n**Growth.** A new optional member arrives in a later version of this component. A reader refuses a member it does not know (the `SealedRecordBody` rules), so a profile carrying one is not shown by a reader that predates it, and a writer SHOULD NOT use a member it cannot expect the room's readers to know. An ecosystem extension goes in `ext`, which a reader that does not understand it ignores. A change no earlier reader could survive is a new statement under a new derivation label (`openvtc/room/profile/v2`), written beside this one until the room's readers have moved.",
/// "type": "object",
/// "required": [
/// "name"
/// ],
/// "properties": {
/// "description": {
/// "description": "What the room is for, in a sentence or a short paragraph. Plain text. Absent when the room has none; an empty string is not a description.",
/// "type": "string",
/// "maxLength": 1000,
/// "minLength": 1
/// },
/// "emoji": {
/// "description": "One emoji — a single extended grapheme cluster, such as `📋` — that a surface MAY show beside the name. Text, rendered as text: never an image, never a URL, never anything a reader fetches, since a fetched icon would tell whoever serves it which member opened which room, and when. A reader that cannot render it shows nothing in its place.",
/// "type": "string",
/// "maxLength": 16,
/// "minLength": 1
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1. A reader ignores what it does not understand.",
/// "$ref": "#/definitions/Ext"
/// },
/// "name": {
/// "description": "The room's name as its members see it. Plain text, one line. Bounded like the label a community gives a room it owns (`RoomLabel` in vtc/_shared), so a community can write the same name here that it shows its administrators and the members it offers the room to.",
/// "type": "string",
/// "maxLength": 128,
/// "minLength": 1
/// },
/// "tags": {
/// "description": "Short identifiers the room files itself under, as a set: order carries no meaning. A member's surface MAY filter or group rooms by them. They are the room's own and shown to its members — distinct from the tags a community files the rooms it owns under for its administrators, which are never written here.",
/// "type": "array",
/// "items": {
/// "type": "string",
/// "maxLength": 32,
/// "minLength": 1,
/// "pattern": "^[a-z0-9][a-z0-9-]{0,31}$"
/// },
/// "maxItems": 8,
/// "uniqueItems": true
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct RoomProfile {
///What the room is for, in a sentence or a short paragraph. Plain text. Absent when the room has none; an empty string is not a description.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub description: ::std::option::Option<RoomProfileDescription>,
///One emoji — a single extended grapheme cluster, such as `📋` — that a surface MAY show beside the name. Text, rendered as text: never an image, never a URL, never anything a reader fetches, since a fetched icon would tell whoever serves it which member opened which room, and when. A reader that cannot render it shows nothing in its place.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub emoji: ::std::option::Option<RoomProfileEmoji>,
///Ecosystem-defined extension members per SPEC.md §4.5.1. A reader ignores what it does not understand.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///The room's name as its members see it. Plain text, one line. Bounded like the label a community gives a room it owns (`RoomLabel` in vtc/_shared), so a community can write the same name here that it shows its administrators and the members it offers the room to.
pub name: RoomProfileName,
///Short identifiers the room files itself under, as a set: order carries no meaning. A member's surface MAY filter or group rooms by them. They are the room's own and shown to its members — distinct from the tags a community files the rooms it owns under for its administrators, which are never written here.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub tags: ::std::option::Option<Vec<RoomProfileTagsItem>>,
}
impl RoomProfile {
pub fn builder() -> builder::RoomProfile {
Default::default()
}
}
///What the room is for, in a sentence or a short paragraph. Plain text. Absent when the room has none; an empty string is not a description.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "What the room is for, in a sentence or a short paragraph. Plain text. Absent when the room has none; an empty string is not a description.",
/// "type": "string",
/// "maxLength": 1000,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct RoomProfileDescription(::std::string::String);
impl ::std::ops::Deref for RoomProfileDescription {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<RoomProfileDescription> for ::std::string::String {
fn from(value: RoomProfileDescription) -> Self {
value.0
}
}
impl ::std::str::FromStr for RoomProfileDescription {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 1000usize {
return Err("longer than 1000 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for RoomProfileDescription {
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 RoomProfileDescription {
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 RoomProfileDescription {
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 RoomProfileDescription {
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())
})
}
}
///One emoji — a single extended grapheme cluster, such as `📋` — that a surface MAY show beside the name. Text, rendered as text: never an image, never a URL, never anything a reader fetches, since a fetched icon would tell whoever serves it which member opened which room, and when. A reader that cannot render it shows nothing in its place.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "One emoji — a single extended grapheme cluster, such as `📋` — that a surface MAY show beside the name. Text, rendered as text: never an image, never a URL, never anything a reader fetches, since a fetched icon would tell whoever serves it which member opened which room, and when. A reader that cannot render it shows nothing in its place.",
/// "type": "string",
/// "maxLength": 16,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct RoomProfileEmoji(::std::string::String);
impl ::std::ops::Deref for RoomProfileEmoji {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<RoomProfileEmoji> for ::std::string::String {
fn from(value: RoomProfileEmoji) -> Self {
value.0
}
}
impl ::std::str::FromStr for RoomProfileEmoji {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 16usize {
return Err("longer than 16 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for RoomProfileEmoji {
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 RoomProfileEmoji {
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 RoomProfileEmoji {
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 RoomProfileEmoji {
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 room's name as its members see it. Plain text, one line. Bounded like the label a community gives a room it owns (`RoomLabel` in vtc/_shared), so a community can write the same name here that it shows its administrators and the members it offers the room to.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The room's name as its members see it. Plain text, one line. Bounded like the label a community gives a room it owns (`RoomLabel` in vtc/_shared), so a community can write the same name here that it shows its administrators and the members it offers the room to.",
/// "type": "string",
/// "maxLength": 128,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct RoomProfileName(::std::string::String);
impl ::std::ops::Deref for RoomProfileName {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<RoomProfileName> for ::std::string::String {
fn from(value: RoomProfileName) -> Self {
value.0
}
}
impl ::std::str::FromStr for RoomProfileName {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 128usize {
return Err("longer than 128 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for RoomProfileName {
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 RoomProfileName {
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 RoomProfileName {
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 RoomProfileName {
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())
})
}
}
///`RoomProfileTagsItem`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "maxLength": 32,
/// "minLength": 1,
/// "pattern": "^[a-z0-9][a-z0-9-]{0,31}$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct RoomProfileTagsItem(::std::string::String);
impl ::std::ops::Deref for RoomProfileTagsItem {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<RoomProfileTagsItem> for ::std::string::String {
fn from(value: RoomProfileTagsItem) -> Self {
value.0
}
}
impl ::std::str::FromStr for RoomProfileTagsItem {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 32usize {
return Err("longer than 32 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new("^[a-z0-9][a-z0-9-]{0,31}$").unwrap()
});
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[a-z0-9][a-z0-9-]{0,31}$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for RoomProfileTagsItem {
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 RoomProfileTagsItem {
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 RoomProfileTagsItem {
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 RoomProfileTagsItem {
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 Response {
epoch: ::std::result::Result<
::std::option::Option<::std::num::NonZeroU64>,
::std::string::String,
>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
key: ::std::result::Result<super::ResponseKey, ::std::string::String>,
outcome: ::std::result::Result<super::ResponseOutcome, ::std::string::String>,
room_id: ::std::result::Result<super::ResponseRoomId, ::std::string::String>,
version: ::std::result::Result<::std::num::NonZeroU64, ::std::string::String>,
}
impl ::std::default::Default for Response {
fn default() -> Self {
Self {
epoch: Ok(Default::default()),
ext: Ok(Default::default()),
key: Err("no value supplied for key".to_string()),
outcome: Err("no value supplied for outcome".to_string()),
room_id: Err("no value supplied for room_id".to_string()),
version: Err("no value supplied for version".to_string()),
}
}
}
impl Response {
pub fn epoch<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<::std::num::NonZeroU64>>,
T::Error: ::std::fmt::Display,
{
self.epoch = value
.try_into()
.map_err(|e| format!("error converting supplied value for epoch: {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
}
pub fn key<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::ResponseKey>,
T::Error: ::std::fmt::Display,
{
self.key = value
.try_into()
.map_err(|e| format!("error converting supplied value for key: {e}"));
self
}
pub fn outcome<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::ResponseOutcome>,
T::Error: ::std::fmt::Display,
{
self.outcome = value
.try_into()
.map_err(|e| format!("error converting supplied value for outcome: {e}"));
self
}
pub fn room_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::ResponseRoomId>,
T::Error: ::std::fmt::Display,
{
self.room_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for room_id: {e}"));
self
}
pub fn version<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::num::NonZeroU64>,
T::Error: ::std::fmt::Display,
{
self.version = value
.try_into()
.map_err(|e| format!("error converting supplied value for version: {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 {
epoch: value.epoch?,
ext: value.ext?,
key: value.key?,
outcome: value.outcome?,
room_id: value.room_id?,
version: value.version?,
})
}
}
impl ::std::convert::From<super::Response> for Response {
fn from(value: super::Response) -> Self {
Self {
epoch: Ok(value.epoch),
ext: Ok(value.ext),
key: Ok(value.key),
outcome: Ok(value.outcome),
room_id: Ok(value.room_id),
version: Ok(value.version),
}
}
}
#[derive(Clone, Debug)]
pub struct RoomProfile {
description: ::std::result::Result<
::std::option::Option<super::RoomProfileDescription>,
::std::string::String,
>,
emoji: ::std::result::Result<
::std::option::Option<super::RoomProfileEmoji>,
::std::string::String,
>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
name: ::std::result::Result<super::RoomProfileName, ::std::string::String>,
tags: ::std::result::Result<
::std::option::Option<Vec<super::RoomProfileTagsItem>>,
::std::string::String,
>,
}
impl ::std::default::Default for RoomProfile {
fn default() -> Self {
Self {
description: Ok(Default::default()),
emoji: Ok(Default::default()),
ext: Ok(Default::default()),
name: Err("no value supplied for name".to_string()),
tags: Ok(Default::default()),
}
}
}
impl RoomProfile {
pub fn description<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::RoomProfileDescription>>,
T::Error: ::std::fmt::Display,
{
self.description = value
.try_into()
.map_err(|e| format!("error converting supplied value for description: {e}"));
self
}
pub fn emoji<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::RoomProfileEmoji>>,
T::Error: ::std::fmt::Display,
{
self.emoji = value
.try_into()
.map_err(|e| format!("error converting supplied value for emoji: {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
}
pub fn name<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::RoomProfileName>,
T::Error: ::std::fmt::Display,
{
self.name = value
.try_into()
.map_err(|e| format!("error converting supplied value for name: {e}"));
self
}
pub fn tags<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<Vec<super::RoomProfileTagsItem>>>,
T::Error: ::std::fmt::Display,
{
self.tags = value
.try_into()
.map_err(|e| format!("error converting supplied value for tags: {e}"));
self
}
}
impl ::std::convert::TryFrom<RoomProfile> for super::RoomProfile {
type Error = super::error::ConversionError;
fn try_from(
value: RoomProfile,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
description: value.description?,
emoji: value.emoji?,
ext: value.ext?,
name: value.name?,
tags: value.tags?,
})
}
}
impl ::std::convert::From<super::RoomProfile> for RoomProfile {
fn from(value: super::RoomProfile) -> Self {
Self {
description: Ok(value.description),
emoji: Ok(value.emoji),
ext: Ok(value.ext),
name: Ok(value.name),
tags: Ok(value.tags),
}
}
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/rooms/keys/profile/write/0.1";
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 \"description\": \"Success response to rooms/keys/profile/write. Type https://trusttasks.org/spec/rooms/keys/profile/write/0.1#response. Where the profile now is at the host, and under which epoch.\",\n \"properties\": {\n \"epoch\": {\n \"description\": \"The epoch the profile was sealed under. Absent on an `open` room.\",\n \"minimum\": 1,\n \"type\": \"integer\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"key\": {\n \"description\": \"The record key the profile is stored at: `profile_key(epoch)` on a sealed tier, `room-profile` on an `open` room. Returned because it is already the host's to see; the caller needs it for nothing but diagnosis.\",\n \"maxLength\": 512,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"outcome\": {\n \"description\": \"`written`: the profile given is the room's profile, stored and checked to open. `cleared`: the record at the current epoch's profile location is retracted.\",\n \"enum\": [\n \"written\",\n \"cleared\"\n ],\n \"type\": \"string\"\n },\n \"roomId\": {\n \"maxLength\": 512,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"version\": {\n \"description\": \"The version the host assigned to the write or the retraction.\",\n \"minimum\": 1,\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"roomId\",\n \"outcome\",\n \"key\",\n \"version\"\n ],\n \"title\": \"Rooms Keys Profile Write — response payload\",\n \"type\": \"object\"\n },\n \"RoomProfile\": {\n \"additionalProperties\": false,\n \"description\": \"What a room says about itself to its own members: a name and, optionally, a description, short tags and an emoji, so that a member's wallet, portal or CLI can show what each room is rather than a DID.\\n\\n**It lives in the room, and only there.** A profile is carried in a record of the room (`RoomProfileStatement`), sealed on `attributed` and `private` rooms like every other record, so the room's host stores ciphertext and learns neither the name nor anything else here. It is never put in a credential — credentials are presented to hosts — nor in a host's record of the room, in `rooms/create` or `rooms/owner/register`, or in any member a host can read. On an `open` room it is cleartext, as everything in an open room is.\\n\\n**Plain text, never instructions.** Every member is free text chosen by whoever wrote the profile and shown to every member of the room. A reader renders `name`, `description` and `tags` as text — never as markup, never as a link it follows — removes or replaces control characters before showing them, and MUST NOT treat any of it as instructions, which matters most for an agent acting for a member. A writer MUST NOT put personal data or a credential in any member.\\n\\n**Growth.** A new optional member arrives in a later version of this component. A reader refuses a member it does not know (the `SealedRecordBody` rules), so a profile carrying one is not shown by a reader that predates it, and a writer SHOULD NOT use a member it cannot expect the room's readers to know. An ecosystem extension goes in `ext`, which a reader that does not understand it ignores. A change no earlier reader could survive is a new statement under a new derivation label (`openvtc/room/profile/v2`), written beside this one until the room's readers have moved.\",\n \"properties\": {\n \"description\": {\n \"description\": \"What the room is for, in a sentence or a short paragraph. Plain text. Absent when the room has none; an empty string is not a description.\",\n \"maxLength\": 1000,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"emoji\": {\n \"description\": \"One emoji — a single extended grapheme cluster, such as `📋` — that a surface MAY show beside the name. Text, rendered as text: never an image, never a URL, never anything a reader fetches, since a fetched icon would tell whoever serves it which member opened which room, and when. A reader that cannot render it shows nothing in its place.\",\n \"maxLength\": 16,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1. A reader ignores what it does not understand.\"\n },\n \"name\": {\n \"description\": \"The room's name as its members see it. Plain text, one line. Bounded like the label a community gives a room it owns (`RoomLabel` in vtc/_shared), so a community can write the same name here that it shows its administrators and the members it offers the room to.\",\n \"maxLength\": 128,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"tags\": {\n \"description\": \"Short identifiers the room files itself under, as a set: order carries no meaning. A member's surface MAY filter or group rooms by them. They are the room's own and shown to its members — distinct from the tags a community files the rooms it owns under for its administrators, which are never written here.\",\n \"items\": {\n \"maxLength\": 32,\n \"minLength\": 1,\n \"pattern\": \"^[a-z0-9][a-z0-9-]{0,31}$\",\n \"type\": \"string\"\n },\n \"maxItems\": 8,\n \"type\": \"array\",\n \"uniqueItems\": true\n }\n },\n \"required\": [\n \"name\"\n ],\n \"title\": \"RoomProfile\",\n \"type\": \"object\"\n }\n },\n \"$id\": \"https://trusttasks.org/spec/rooms/keys/profile/write/0.1\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"anyOf\": [\n {\n \"required\": [\n \"profile\"\n ]\n },\n {\n \"required\": [\n \"clear\"\n ]\n }\n ],\n \"dependentSchemas\": {\n \"clear\": {\n \"not\": {\n \"required\": [\n \"profile\"\n ]\n }\n }\n },\n \"description\": \"A member holding `curate` or `admin` in a room asks their own key holder to set what the room is called and what it is for — or to clear it. The key holder signs the profile as the member, with the member's authority chain inside it, seals it under the room's current epoch, and stores it at the host at the location only members can compute. Exactly one of `profile` and `clear`. The outer document members are owned by the framework — SPEC §6.3.\",\n \"properties\": {\n \"clear\": {\n \"const\": true,\n \"description\": \"Remove the room's profile: retract the record at the current epoch's profile location, so that readers find `cleared` there and stop. Only `true` is meaningful, so only `true` is accepted. Takes `curate`, which the host enforces on a retraction. Mutually exclusive with `profile`.\",\n \"type\": \"boolean\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n },\n \"host\": {\n \"description\": \"The host to write to, as a DID. Named by the caller for the reason `rooms/keys/read` gives. A room registered with more than one host is written at the one that takes its writes.\",\n \"maxLength\": 2048,\n \"minLength\": 1,\n \"pattern\": \"^did:\",\n \"type\": \"string\"\n },\n \"profile\": {\n \"$ref\": \"#/$defs/RoomProfile\",\n \"description\": \"The room's new profile, replacing the whole of the current one: a member absent here is absent from the room's profile afterwards. Mutually exclusive with `clear`.\"\n },\n \"roomId\": {\n \"description\": \"The room whose profile to set. The recipient MUST hold its principal's membership of it.\",\n \"maxLength\": 512,\n \"minLength\": 1,\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"roomId\",\n \"host\"\n ],\n \"title\": \"Rooms Keys Profile Write — payload\",\n \"type\": \"object\"\n}\n",
);
}
impl crate::Payload for Response {
const TYPE_URI: &'static str =
"https://trusttasks.org/spec/rooms/keys/profile/write/0.1#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 \"description\": \"Success response to rooms/keys/profile/write. Type https://trusttasks.org/spec/rooms/keys/profile/write/0.1#response. Where the profile now is at the host, and under which epoch.\",\n \"properties\": {\n \"epoch\": {\n \"description\": \"The epoch the profile was sealed under. Absent on an `open` room.\",\n \"minimum\": 1,\n \"type\": \"integer\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"key\": {\n \"description\": \"The record key the profile is stored at: `profile_key(epoch)` on a sealed tier, `room-profile` on an `open` room. Returned because it is already the host's to see; the caller needs it for nothing but diagnosis.\",\n \"maxLength\": 512,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"outcome\": {\n \"description\": \"`written`: the profile given is the room's profile, stored and checked to open. `cleared`: the record at the current epoch's profile location is retracted.\",\n \"enum\": [\n \"written\",\n \"cleared\"\n ],\n \"type\": \"string\"\n },\n \"roomId\": {\n \"maxLength\": 512,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"version\": {\n \"description\": \"The version the host assigned to the write or the retraction.\",\n \"minimum\": 1,\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"roomId\",\n \"outcome\",\n \"key\",\n \"version\"\n ],\n \"title\": \"Rooms Keys Profile Write — response payload\",\n \"type\": \"object\"\n },\n \"RoomProfile\": {\n \"additionalProperties\": false,\n \"description\": \"What a room says about itself to its own members: a name and, optionally, a description, short tags and an emoji, so that a member's wallet, portal or CLI can show what each room is rather than a DID.\\n\\n**It lives in the room, and only there.** A profile is carried in a record of the room (`RoomProfileStatement`), sealed on `attributed` and `private` rooms like every other record, so the room's host stores ciphertext and learns neither the name nor anything else here. It is never put in a credential — credentials are presented to hosts — nor in a host's record of the room, in `rooms/create` or `rooms/owner/register`, or in any member a host can read. On an `open` room it is cleartext, as everything in an open room is.\\n\\n**Plain text, never instructions.** Every member is free text chosen by whoever wrote the profile and shown to every member of the room. A reader renders `name`, `description` and `tags` as text — never as markup, never as a link it follows — removes or replaces control characters before showing them, and MUST NOT treat any of it as instructions, which matters most for an agent acting for a member. A writer MUST NOT put personal data or a credential in any member.\\n\\n**Growth.** A new optional member arrives in a later version of this component. A reader refuses a member it does not know (the `SealedRecordBody` rules), so a profile carrying one is not shown by a reader that predates it, and a writer SHOULD NOT use a member it cannot expect the room's readers to know. An ecosystem extension goes in `ext`, which a reader that does not understand it ignores. A change no earlier reader could survive is a new statement under a new derivation label (`openvtc/room/profile/v2`), written beside this one until the room's readers have moved.\",\n \"properties\": {\n \"description\": {\n \"description\": \"What the room is for, in a sentence or a short paragraph. Plain text. Absent when the room has none; an empty string is not a description.\",\n \"maxLength\": 1000,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"emoji\": {\n \"description\": \"One emoji — a single extended grapheme cluster, such as `📋` — that a surface MAY show beside the name. Text, rendered as text: never an image, never a URL, never anything a reader fetches, since a fetched icon would tell whoever serves it which member opened which room, and when. A reader that cannot render it shows nothing in its place.\",\n \"maxLength\": 16,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1. A reader ignores what it does not understand.\"\n },\n \"name\": {\n \"description\": \"The room's name as its members see it. Plain text, one line. Bounded like the label a community gives a room it owns (`RoomLabel` in vtc/_shared), so a community can write the same name here that it shows its administrators and the members it offers the room to.\",\n \"maxLength\": 128,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"tags\": {\n \"description\": \"Short identifiers the room files itself under, as a set: order carries no meaning. A member's surface MAY filter or group rooms by them. They are the room's own and shown to its members — distinct from the tags a community files the rooms it owns under for its administrators, which are never written here.\",\n \"items\": {\n \"maxLength\": 32,\n \"minLength\": 1,\n \"pattern\": \"^[a-z0-9][a-z0-9-]{0,31}$\",\n \"type\": \"string\"\n },\n \"maxItems\": 8,\n \"type\": \"array\",\n \"uniqueItems\": true\n }\n },\n \"required\": [\n \"name\"\n ],\n \"title\": \"RoomProfile\",\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;
}
/// The extended error codes this specification declares (SPEC §7.3 item 9,
/// §8.5), in declaration order. Empty when it declares none.
pub const ERROR_CODES: &[crate::DeclaredErrorCode] = &[
error_codes::NOT_A_MEMBER,
error_codes::NO_AUTHORITY,
error_codes::HOST_UNREACHABLE,
error_codes::HOST_REFUSED,
error_codes::CONTENDED,
];
/// One constant per extended error code this specification declares
/// (SPEC §7.3 item 9), named for its local part.
///
/// Emit these rather than a string literal: the code is read from the
/// specification, so it cannot name a code the specification never
/// declared.
pub mod error_codes {
/// `rooms/keys/profile/write:notAMember`
///
/// The recipient holds no membership of this room for its principal — no group state on a sealed tier, no membership credential on an `open` one.
///
/// Declared `retryable: false`.
pub const NOT_A_MEMBER: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "rooms/keys/profile/write:notAMember",
retryable: false,
details_schema: ::core::option::Option::None,
};
/// `rooms/keys/profile/write:noAuthority`
///
/// The authority its principal holds for this room does not suffice: writing a profile takes `write`, to store it, and `curate` or `admin`, for readers to accept it; clearing one takes `curate`. Refused here rather than stored, because a profile readers will refuse is worse than none.
///
/// Declared `retryable: false`.
pub const NO_AUTHORITY: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "rooms/keys/profile/write:noAuthority",
retryable: false,
details_schema: ::core::option::Option::None,
};
/// `rooms/keys/profile/write:hostUnreachable`
///
/// The named host could not be resolved, advertises no transport this recipient speaks, or did not answer. Nothing is known to have been stored.
///
/// Declared `retryable: true`.
pub const HOST_UNREACHABLE: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "rooms/keys/profile/write:hostUnreachable",
retryable: true,
details_schema: ::core::option::Option::None,
};
/// `rooms/keys/profile/write:hostRefused`
///
/// The host answered and declined — commonly that the room's epoch has lapsed and it is read-only until renewed, that it does not serve this room, or that it refused the presentation. Its own code and message are carried in `details`.
///
/// Declared `retryable: false`.
/// Declares a `detailsSchema` for the error's `details` member.
pub const HOST_REFUSED: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "rooms/keys/profile/write:hostRefused",
retryable: false,
details_schema: ::core::option::Option::Some(
"{\"additionalProperties\":false,\"properties\":{\"code\":{\"maxLength\":256,\"type\":\"string\"},\"message\":{\"maxLength\":500,\"type\":\"string\"}},\"type\":\"object\"}",
),
};
/// `rooms/keys/profile/write:contended`
///
/// Other writes to the room kept landing between sealing the profile and storing it, so the host assigned a version other than the one the record was sealed for, and the record it stored does not open. Readers ignore such a record and fall back to the profile before it. Retrying is safe.
///
/// Declared `retryable: true`.
pub const CONTENDED: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "rooms/keys/profile/write:contended",
retryable: true,
details_schema: ::core::option::Option::None,
};
}