//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `git-ns/bridge/job`. Version: `0.2`.
#[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())
}
}
}
///The forge owner whose control the binding admin is to prove.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "BindTarget",
/// "description": "The forge owner whose control the binding admin is to prove.",
/// "type": "object",
/// "required": [
/// "forge",
/// "owner"
/// ],
/// "properties": {
/// "forge": {
/// "$ref": "#/definitions/ForgeHost"
/// },
/// "owner": {
/// "$ref": "#/definitions/Segment"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct BindTarget {
pub forge: ForgeHost,
pub owner: Segment,
}
impl BindTarget {
pub fn builder() -> builder::BindTarget {
Default::default()
}
}
///`DesiredRole`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "object",
/// "required": [
/// "account",
/// "right",
/// "subject"
/// ],
/// "properties": {
/// "account": {
/// "description": "The subject's linked account on this namespace's forge.",
/// "$ref": "#/definitions/ForgeAccount"
/// },
/// "right": {
/// "description": "The subject's highest effective right on the target, which the forge adapter maps to one of its roles.",
/// "$ref": "#/definitions/Right"
/// },
/// "subject": {
/// "$ref": "#/definitions/Did"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct DesiredRole {
///The subject's linked account on this namespace's forge.
pub account: ForgeAccount,
///The subject's highest effective right on the target, which the forge adapter maps to one of its roles.
pub right: Right,
pub subject: Did,
}
impl DesiredRole {
pub fn builder() -> builder::DesiredRole {
Default::default()
}
}
///A DID, compared by exact string equality.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Did",
/// "description": "A DID, compared by exact string equality.",
/// "type": "string",
/// "maxLength": 2048,
/// "pattern": "^did:[a-z0-9]+:\\S+$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct Did(::std::string::String);
impl ::std::ops::Deref for Did {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<Did> for ::std::string::String {
fn from(value: Did) -> Self {
value.0
}
}
impl ::std::str::FromStr for Did {
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());
}
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| ::regress::Regex::new("^did:[a-z0-9]+:\\S+$").unwrap());
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^did:[a-z0-9]+:\\S+$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for Did {
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 Did {
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 Did {
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 Did {
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())
})
}
}
///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 person's account on one forge. `id` is authoritative; `login` is for display only, because logins can be renamed and re-registered.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "ForgeAccount",
/// "description": "A person's account on one forge. `id` is authoritative; `login` is for display only, because logins can be renamed and re-registered.",
/// "type": "object",
/// "required": [
/// "forge",
/// "id",
/// "login"
/// ],
/// "properties": {
/// "forge": {
/// "$ref": "#/definitions/ForgeHost"
/// },
/// "id": {
/// "$ref": "#/definitions/ForgeId"
/// },
/// "login": {
/// "description": "The account's current login, as the forge reported it when last seen. Display only.",
/// "type": "string",
/// "maxLength": 100,
/// "minLength": 1
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct ForgeAccount {
pub forge: ForgeHost,
pub id: ForgeId,
///The account's current login, as the forge reported it when last seen. Display only.
pub login: ForgeAccountLogin,
}
impl ForgeAccount {
pub fn builder() -> builder::ForgeAccount {
Default::default()
}
}
///The account's current login, as the forge reported it when last seen. Display only.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The account's current login, as the forge reported it when last seen. Display only.",
/// "type": "string",
/// "maxLength": 100,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ForgeAccountLogin(::std::string::String);
impl ::std::ops::Deref for ForgeAccountLogin {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ForgeAccountLogin> for ::std::string::String {
fn from(value: ForgeAccountLogin) -> Self {
value.0
}
}
impl ::std::str::FromStr for ForgeAccountLogin {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 100usize {
return Err("longer than 100 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 ForgeAccountLogin {
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 ForgeAccountLogin {
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 ForgeAccountLogin {
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 ForgeAccountLogin {
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 lowercased DNS host of a forge: `github.com`, a GitHub Enterprise Server host, `codeberg.org`, or a self-hosted Forgejo instance such as `git.example.org`. No scheme, no port, no path. The host is a segment of every resource, so a right never crosses forges.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "ForgeHost",
/// "description": "The lowercased DNS host of a forge: `github.com`, a GitHub Enterprise Server host, `codeberg.org`, or a self-hosted Forgejo instance such as `git.example.org`. No scheme, no port, no path. The host is a segment of every resource, so a right never crosses forges.",
/// "type": "string",
/// "maxLength": 253,
/// "minLength": 3,
/// "pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ForgeHost(::std::string::String);
impl ::std::ops::Deref for ForgeHost {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ForgeHost> for ::std::string::String {
fn from(value: ForgeHost) -> Self {
value.0
}
}
impl ::std::str::FromStr for ForgeHost {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 253usize {
return Err("longer than 253 characters".into());
}
if value.chars().count() < 3usize {
return Err("shorter than 3 characters".into());
}
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new(
"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$",
)
.unwrap()
});
if PATTERN.find(value).is_none() {
return Err(
"doesn't match pattern \"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$\""
.into(),
);
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for ForgeHost {
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 ForgeHost {
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 ForgeHost {
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 ForgeHost {
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())
})
}
}
///An identifier the forge itself assigns — a repository id, a user or organisation id — carried as a string so a forge whose ids are not numbers needs no new version. GitHub and Forgejo ids are decimal integers written as strings (`"812736451"`). Unlike a name, it survives renames and transfers, which is why rights and bindings are keyed by it.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "ForgeId",
/// "description": "An identifier the forge itself assigns — a repository id, a user or organisation id — carried as a string so a forge whose ids are not numbers needs no new version. GitHub and Forgejo ids are decimal integers written as strings (`\"812736451\"`). Unlike a name, it survives renames and transfers, which is why rights and bindings are keyed by it.",
/// "type": "string",
/// "maxLength": 64,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ForgeId(::std::string::String);
impl ::std::ops::Deref for ForgeId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ForgeId> for ::std::string::String {
fn from(value: ForgeId) -> Self {
value.0
}
}
impl ::std::str::FromStr for ForgeId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 64usize {
return Err("longer than 64 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 ForgeId {
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 ForgeId {
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 ForgeId {
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 ForgeId {
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 VTC's identifier for a job, unique across the VTC's jobs.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The VTC's identifier for a job, unique across the VTC's jobs.",
/// "type": "string",
/// "maxLength": 128,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct JobId(::std::string::String);
impl ::std::ops::Deref for JobId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<JobId> for ::std::string::String {
fn from(value: JobId) -> Self {
value.0
}
}
impl ::std::str::FromStr for JobId {
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 JobId {
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 JobId {
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 JobId {
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 JobId {
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 VTC's opaque identifier for a namespace, assigned when it is bound. Stable for the life of the binding; never reused for another binding.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "NamespaceId",
/// "description": "The VTC's opaque identifier for a namespace, assigned when it is bound. Stable for the life of the binding; never reused for another binding.",
/// "type": "string",
/// "maxLength": 128,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct NamespaceId(::std::string::String);
impl ::std::ops::Deref for NamespaceId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<NamespaceId> for ::std::string::String {
fn from(value: NamespaceId) -> Self {
value.0
}
}
impl ::std::str::FromStr for NamespaceId {
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 NamespaceId {
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 NamespaceId {
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 NamespaceId {
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 NamespaceId {
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 VTC asks its bridge to do one piece of forge work in a namespace. The job names what to do (`kind`) and carries the desired state, never forge credentials — the bridge holds those. Which optional members a job carries is fixed by its `kind` (see the specification's kind table); a bridge refuses a job that carries a member its kind does not use, or lacks one its kind requires.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/git-ns/bridge/job/0.2",
/// "title": "Payload",
/// "description": "The VTC asks its bridge to do one piece of forge work in a namespace. The job names what to do (`kind`) and carries the desired state, never forge credentials — the bridge holds those. Which optional members a job carries is fixed by its `kind` (see the specification's kind table); a bridge refuses a job that carries a member its kind does not use, or lacks one its kind requires.",
/// "type": "object",
/// "required": [
/// "jobId",
/// "kind",
/// "namespace"
/// ],
/// "properties": {
/// "desiredRoles": {
/// "description": "The complete set of people who should hold a forge role on the target. The bridge converges to exactly this set among the roles it manages: it adds or changes roles to match and removes managed roles not listed. `projectRoles` and `createRepo` only.",
/// "type": "array",
/// "items": {
/// "$ref": "#/definitions/DesiredRole"
/// },
/// "uniqueItems": true
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "jobId": {
/// "description": "The VTC's identifier for this job. The bridge reports on it with git-ns/bridge/result.",
/// "$ref": "#/definitions/JobId"
/// },
/// "kind": {
/// "description": "What to do. `projectRoles` — converge forge roles to `desiredRoles`, and remove whatever role each of `removeAccounts` holds on `repo`. `createRepo` — create `repo`, run the bootstrap plan, and project `desiredRoles`. `bootstrap` — run the bootstrap plan, or the listed `steps` of it, on an existing `repo`. `archive` — archive `repo`. `inspect` — compare the forge with the projection for `repo`, or for the whole namespace. `beginBind` — start the proof that the binding admin controls `target`. `beginAccountLink` — start linking `subject`'s account on this namespace's forge.",
/// "type": "string",
/// "enum": [
/// "projectRoles",
/// "createRepo",
/// "bootstrap",
/// "archive",
/// "inspect",
/// "beginBind",
/// "beginAccountLink"
/// ]
/// },
/// "namespace": {
/// "description": "The namespace the job acts in. It also selects the bridge's forge credentials for it.",
/// "$ref": "#/definitions/NamespaceId"
/// },
/// "removeAccounts": {
/// "description": "Accounts whose direct role on `repo` the bridge removes, whatever that role is — including a role the bridge does not manage and would otherwise only report as drift. Matched by `forge` and `id`; `login` is display only. No account listed here may also appear in `desiredRoles`. `projectRoles` with `repo` only.",
/// "type": "array",
/// "items": {
/// "$ref": "#/definitions/ForgeAccount"
/// },
/// "minItems": 1,
/// "uniqueItems": true
/// },
/// "repo": {
/// "description": "The repository. Required for `createRepo`, `bootstrap` and `archive`. For `projectRoles`, absent means the namespace-level roles (organisation owners), where only `git.ns.admin` is projected. For `inspect`, absent means a sweep of every repository in the namespace.",
/// "$ref": "#/definitions/RepoResource"
/// },
/// "spec": {
/// "description": "How to create the repository. `createRepo` only.",
/// "$ref": "#/definitions/RepoSpec"
/// },
/// "steps": {
/// "description": "Run only these steps of the adapter's bootstrap plan, in the plan's order. Absent: the whole plan. `bootstrap` only.",
/// "type": "array",
/// "items": {
/// "$ref": "#/definitions/StepName"
/// },
/// "minItems": 1,
/// "uniqueItems": true
/// },
/// "subject": {
/// "description": "The member whose forge account is being linked. `beginAccountLink` only.",
/// "$ref": "#/definitions/Did"
/// },
/// "target": {
/// "description": "`beginBind` only.",
/// "$ref": "#/definitions/BindTarget"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Payload {
///The complete set of people who should hold a forge role on the target. The bridge converges to exactly this set among the roles it manages: it adds or changes roles to match and removes managed roles not listed. `projectRoles` and `createRepo` only.
#[serde(
rename = "desiredRoles",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub desired_roles: ::std::option::Option<Vec<DesiredRole>>,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///The VTC's identifier for this job. The bridge reports on it with git-ns/bridge/result.
#[serde(rename = "jobId")]
pub job_id: JobId,
///What to do. `projectRoles` — converge forge roles to `desiredRoles`, and remove whatever role each of `removeAccounts` holds on `repo`. `createRepo` — create `repo`, run the bootstrap plan, and project `desiredRoles`. `bootstrap` — run the bootstrap plan, or the listed `steps` of it, on an existing `repo`. `archive` — archive `repo`. `inspect` — compare the forge with the projection for `repo`, or for the whole namespace. `beginBind` — start the proof that the binding admin controls `target`. `beginAccountLink` — start linking `subject`'s account on this namespace's forge.
pub kind: PayloadKind,
///The namespace the job acts in. It also selects the bridge's forge credentials for it.
pub namespace: NamespaceId,
///Accounts whose direct role on `repo` the bridge removes, whatever that role is — including a role the bridge does not manage and would otherwise only report as drift. Matched by `forge` and `id`; `login` is display only. No account listed here may also appear in `desiredRoles`. `projectRoles` with `repo` only.
#[serde(
rename = "removeAccounts",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub remove_accounts: ::std::option::Option<Vec<ForgeAccount>>,
///The repository. Required for `createRepo`, `bootstrap` and `archive`. For `projectRoles`, absent means the namespace-level roles (organisation owners), where only `git.ns.admin` is projected. For `inspect`, absent means a sweep of every repository in the namespace.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub repo: ::std::option::Option<RepoResource>,
///How to create the repository. `createRepo` only.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub spec: ::std::option::Option<RepoSpec>,
///Run only these steps of the adapter's bootstrap plan, in the plan's order. Absent: the whole plan. `bootstrap` only.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub steps: ::std::option::Option<Vec<StepName>>,
///The member whose forge account is being linked. `beginAccountLink` only.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub subject: ::std::option::Option<Did>,
///`beginBind` only.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub target: ::std::option::Option<BindTarget>,
}
impl Payload {
pub fn builder() -> builder::Payload {
Default::default()
}
}
///What to do. `projectRoles` — converge forge roles to `desiredRoles`, and remove whatever role each of `removeAccounts` holds on `repo`. `createRepo` — create `repo`, run the bootstrap plan, and project `desiredRoles`. `bootstrap` — run the bootstrap plan, or the listed `steps` of it, on an existing `repo`. `archive` — archive `repo`. `inspect` — compare the forge with the projection for `repo`, or for the whole namespace. `beginBind` — start the proof that the binding admin controls `target`. `beginAccountLink` — start linking `subject`'s account on this namespace's forge.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "What to do. `projectRoles` — converge forge roles to `desiredRoles`, and remove whatever role each of `removeAccounts` holds on `repo`. `createRepo` — create `repo`, run the bootstrap plan, and project `desiredRoles`. `bootstrap` — run the bootstrap plan, or the listed `steps` of it, on an existing `repo`. `archive` — archive `repo`. `inspect` — compare the forge with the projection for `repo`, or for the whole namespace. `beginBind` — start the proof that the binding admin controls `target`. `beginAccountLink` — start linking `subject`'s account on this namespace's forge.",
/// "type": "string",
/// "enum": [
/// "projectRoles",
/// "createRepo",
/// "bootstrap",
/// "archive",
/// "inspect",
/// "beginBind",
/// "beginAccountLink"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum PayloadKind {
#[serde(rename = "projectRoles")]
ProjectRoles,
#[serde(rename = "createRepo")]
CreateRepo,
#[serde(rename = "bootstrap")]
Bootstrap,
#[serde(rename = "archive")]
Archive,
#[serde(rename = "inspect")]
Inspect,
#[serde(rename = "beginBind")]
BeginBind,
#[serde(rename = "beginAccountLink")]
BeginAccountLink,
}
impl ::std::fmt::Display for PayloadKind {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::ProjectRoles => f.write_str("projectRoles"),
Self::CreateRepo => f.write_str("createRepo"),
Self::Bootstrap => f.write_str("bootstrap"),
Self::Archive => f.write_str("archive"),
Self::Inspect => f.write_str("inspect"),
Self::BeginBind => f.write_str("beginBind"),
Self::BeginAccountLink => f.write_str("beginAccountLink"),
}
}
}
impl ::std::str::FromStr for PayloadKind {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"projectRoles" => Ok(Self::ProjectRoles),
"createRepo" => Ok(Self::CreateRepo),
"bootstrap" => Ok(Self::Bootstrap),
"archive" => Ok(Self::Archive),
"inspect" => Ok(Self::Inspect),
"beginBind" => Ok(Self::BeginBind),
"beginAccountLink" => Ok(Self::BeginAccountLink),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for PayloadKind {
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 PayloadKind {
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 PayloadKind {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///A forge-qualified resource naming exactly one repository: `<forge-host>/<owner>/<repo>`, lowercase.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "RepoResource",
/// "description": "A forge-qualified resource naming exactly one repository: `<forge-host>/<owner>/<repo>`, lowercase.",
/// "type": "string",
/// "maxLength": 455,
/// "pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+/[a-z0-9_-][a-z0-9._-]{0,99}/[a-z0-9_-][a-z0-9._-]{0,99}$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct RepoResource(::std::string::String);
impl ::std::ops::Deref for RepoResource {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<RepoResource> for ::std::string::String {
fn from(value: RepoResource) -> Self {
value.0
}
}
impl ::std::str::FromStr for RepoResource {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 455usize {
return Err("longer than 455 characters".into());
}
static PATTERN: ::std::sync::LazyLock<::regress::Regex> = ::std::sync::LazyLock::new(
|| {
::regress::Regex::new(
"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+/[a-z0-9_-][a-z0-9._-]{0,99}/[a-z0-9_-][a-z0-9._-]{0,99}$",
)
.unwrap()
},
);
if PATTERN.find(value).is_none() {
return Err(
"doesn't match pattern \"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+/[a-z0-9_-][a-z0-9._-]{0,99}/[a-z0-9_-][a-z0-9._-]{0,99}$\""
.into(),
);
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for RepoResource {
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 RepoResource {
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 RepoResource {
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 RepoResource {
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())
})
}
}
///`RepoSpec`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "object",
/// "required": [
/// "visibility"
/// ],
/// "properties": {
/// "description": {
/// "type": "string",
/// "maxLength": 350
/// },
/// "visibility": {
/// "$ref": "#/definitions/RepoVisibility"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct RepoSpec {
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub description: ::std::option::Option<RepoSpecDescription>,
pub visibility: RepoVisibility,
}
impl RepoSpec {
pub fn builder() -> builder::RepoSpec {
Default::default()
}
}
///`RepoSpecDescription`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "maxLength": 350
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct RepoSpecDescription(::std::string::String);
impl ::std::ops::Deref for RepoSpecDescription {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<RepoSpecDescription> for ::std::string::String {
fn from(value: RepoSpecDescription) -> Self {
value.0
}
}
impl ::std::str::FromStr for RepoSpecDescription {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 350usize {
return Err("longer than 350 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for RepoSpecDescription {
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 RepoSpecDescription {
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 RepoSpecDescription {
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 RepoSpecDescription {
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())
})
}
}
///Repository visibility on the forge.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "RepoVisibility",
/// "description": "Repository visibility on the forge.",
/// "type": "string",
/// "enum": [
/// "public",
/// "private"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum RepoVisibility {
#[serde(rename = "public")]
Public,
#[serde(rename = "private")]
Private,
}
impl ::std::fmt::Display for RepoVisibility {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Public => f.write_str("public"),
Self::Private => f.write_str("private"),
}
}
}
impl ::std::str::FromStr for RepoVisibility {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"public" => Ok(Self::Public),
"private" => Ok(Self::Private),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for RepoVisibility {
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 RepoVisibility {
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 RepoVisibility {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///Whether the bridge took the job.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "description": "Whether the bridge took the job.",
/// "type": "object",
/// "required": [
/// "accepted",
/// "jobId"
/// ],
/// "properties": {
/// "accepted": {
/// "description": "`true` — the job is durably queued, or was already queued or running under this `jobId`; a git-ns/bridge/result follows. `false` — the bridge already finished this `jobId`; it does not run it again, and sends its git-ns/bridge/result again.",
/// "type": "boolean"
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "jobId": {
/// "$ref": "#/definitions/JobId"
/// },
/// "next": {
/// "description": "For `beginBind` and `beginAccountLink` when `accepted` is `true`: where the VTC sends the person.",
/// "type": "object",
/// "required": [
/// "expiresAt",
/// "url"
/// ],
/// "properties": {
/// "expiresAt": {
/// "type": "string",
/// "format": "date-time"
/// },
/// "url": {
/// "type": "string",
/// "format": "uri",
/// "maxLength": 2048,
/// "pattern": "^https://"
/// },
/// "userCode": {
/// "description": "The device-flow code, for a forge that uses one.",
/// "type": "string",
/// "maxLength": 64,
/// "minLength": 1
/// }
/// },
/// "additionalProperties": false
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "response"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Response {
///`true` — the job is durably queued, or was already queued or running under this `jobId`; a git-ns/bridge/result follows. `false` — the bridge already finished this `jobId`; it does not run it again, and sends its git-ns/bridge/result again.
pub accepted: bool,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
#[serde(rename = "jobId")]
pub job_id: JobId,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub next: ::std::option::Option<ResponseNext>,
}
impl Response {
pub fn builder() -> builder::Response {
Default::default()
}
}
///For `beginBind` and `beginAccountLink` when `accepted` is `true`: where the VTC sends the person.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "For `beginBind` and `beginAccountLink` when `accepted` is `true`: where the VTC sends the person.",
/// "type": "object",
/// "required": [
/// "expiresAt",
/// "url"
/// ],
/// "properties": {
/// "expiresAt": {
/// "type": "string",
/// "format": "date-time"
/// },
/// "url": {
/// "type": "string",
/// "format": "uri",
/// "maxLength": 2048,
/// "pattern": "^https://"
/// },
/// "userCode": {
/// "description": "The device-flow code, for a forge that uses one.",
/// "type": "string",
/// "maxLength": 64,
/// "minLength": 1
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct ResponseNext {
#[serde(rename = "expiresAt")]
pub expires_at: ::chrono::DateTime<::chrono::offset::Utc>,
pub url: ::std::string::String,
///The device-flow code, for a forge that uses one.
#[serde(
rename = "userCode",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub user_code: ::std::option::Option<ResponseNextUserCode>,
}
impl ResponseNext {
pub fn builder() -> builder::ResponseNext {
Default::default()
}
}
///The device-flow code, for a forge that uses one.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The device-flow code, for a forge that uses one.",
/// "type": "string",
/// "maxLength": 64,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ResponseNextUserCode(::std::string::String);
impl ::std::ops::Deref for ResponseNextUserCode {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ResponseNextUserCode> for ::std::string::String {
fn from(value: ResponseNextUserCode) -> Self {
value.0
}
}
impl ::std::str::FromStr for ResponseNextUserCode {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 64usize {
return Err("longer than 64 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 ResponseNextUserCode {
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 ResponseNextUserCode {
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 ResponseNextUserCode {
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 ResponseNextUserCode {
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 of the five git rights. Each string is also the TRQP `action` the VTC publishes the right under in its Trust Registry, so it is carried verbatim. `git.ns.admin` and `git.repo.create` apply to a namespace resource; `git.repo.own` and `git.repo.maintain` to a repository resource; `git.commit.sign` to either.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Right",
/// "description": "One of the five git rights. Each string is also the TRQP `action` the VTC publishes the right under in its Trust Registry, so it is carried verbatim. `git.ns.admin` and `git.repo.create` apply to a namespace resource; `git.repo.own` and `git.repo.maintain` to a repository resource; `git.commit.sign` to either.",
/// "type": "string",
/// "enum": [
/// "git.ns.admin",
/// "git.repo.create",
/// "git.repo.own",
/// "git.repo.maintain",
/// "git.commit.sign"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum Right {
#[serde(rename = "git.ns.admin")]
GitNsAdmin,
#[serde(rename = "git.repo.create")]
GitRepoCreate,
#[serde(rename = "git.repo.own")]
GitRepoOwn,
#[serde(rename = "git.repo.maintain")]
GitRepoMaintain,
#[serde(rename = "git.commit.sign")]
GitCommitSign,
}
impl ::std::fmt::Display for Right {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::GitNsAdmin => f.write_str("git.ns.admin"),
Self::GitRepoCreate => f.write_str("git.repo.create"),
Self::GitRepoOwn => f.write_str("git.repo.own"),
Self::GitRepoMaintain => f.write_str("git.repo.maintain"),
Self::GitCommitSign => f.write_str("git.commit.sign"),
}
}
}
impl ::std::str::FromStr for Right {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"git.ns.admin" => Ok(Self::GitNsAdmin),
"git.repo.create" => Ok(Self::GitRepoCreate),
"git.repo.own" => Ok(Self::GitRepoOwn),
"git.repo.maintain" => Ok(Self::GitRepoMaintain),
"git.commit.sign" => Ok(Self::GitCommitSign),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for Right {
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 Right {
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 Right {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///One lowercased owner or repository name. Forges compare these case-insensitively, so the wire form is always lowercase and a producer lowercases before sending. A leading `.` is refused, which rules out `.` and `..`.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Segment",
/// "description": "One lowercased owner or repository name. Forges compare these case-insensitively, so the wire form is always lowercase and a producer lowercases before sending. A leading `.` is refused, which rules out `.` and `..`.",
/// "type": "string",
/// "maxLength": 100,
/// "minLength": 1,
/// "pattern": "^[a-z0-9_-][a-z0-9._-]*$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct Segment(::std::string::String);
impl ::std::ops::Deref for Segment {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<Segment> for ::std::string::String {
fn from(value: Segment) -> Self {
value.0
}
}
impl ::std::str::FromStr for Segment {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 100usize {
return Err("longer than 100 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._-]*$").unwrap()
});
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[a-z0-9_-][a-z0-9._-]*$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for Segment {
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 Segment {
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 Segment {
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 Segment {
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 step of a forge adapter's plan. Forge-neutral names: `create`, `workflow`, `keyring`, `variables`, `requiredCheck`, `roles`, `archive`. An adapter MAY add its own (`mergeStyle`, `runner`), which a VTC displays but need not understand.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "One step of a forge adapter's plan. Forge-neutral names: `create`, `workflow`, `keyring`, `variables`, `requiredCheck`, `roles`, `archive`. An adapter MAY add its own (`mergeStyle`, `runner`), which a VTC displays but need not understand.",
/// "type": "string",
/// "maxLength": 64,
/// "minLength": 1,
/// "pattern": "^[a-z][A-Za-z0-9]*$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct StepName(::std::string::String);
impl ::std::ops::Deref for StepName {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<StepName> for ::std::string::String {
fn from(value: StepName) -> Self {
value.0
}
}
impl ::std::str::FromStr for StepName {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 64usize {
return Err("longer than 64 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-z][A-Za-z0-9]*$").unwrap());
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[a-z][A-Za-z0-9]*$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for StepName {
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 StepName {
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 StepName {
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 StepName {
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 BindTarget {
forge: ::std::result::Result<super::ForgeHost, ::std::string::String>,
owner: ::std::result::Result<super::Segment, ::std::string::String>,
}
impl ::std::default::Default for BindTarget {
fn default() -> Self {
Self {
forge: Err("no value supplied for forge".to_string()),
owner: Err("no value supplied for owner".to_string()),
}
}
}
impl BindTarget {
pub fn forge<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::ForgeHost>,
T::Error: ::std::fmt::Display,
{
self.forge = value
.try_into()
.map_err(|e| format!("error converting supplied value for forge: {e}"));
self
}
pub fn owner<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::Segment>,
T::Error: ::std::fmt::Display,
{
self.owner = value
.try_into()
.map_err(|e| format!("error converting supplied value for owner: {e}"));
self
}
}
impl ::std::convert::TryFrom<BindTarget> for super::BindTarget {
type Error = super::error::ConversionError;
fn try_from(
value: BindTarget,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
forge: value.forge?,
owner: value.owner?,
})
}
}
impl ::std::convert::From<super::BindTarget> for BindTarget {
fn from(value: super::BindTarget) -> Self {
Self {
forge: Ok(value.forge),
owner: Ok(value.owner),
}
}
}
#[derive(Clone, Debug)]
pub struct DesiredRole {
account: ::std::result::Result<super::ForgeAccount, ::std::string::String>,
right: ::std::result::Result<super::Right, ::std::string::String>,
subject: ::std::result::Result<super::Did, ::std::string::String>,
}
impl ::std::default::Default for DesiredRole {
fn default() -> Self {
Self {
account: Err("no value supplied for account".to_string()),
right: Err("no value supplied for right".to_string()),
subject: Err("no value supplied for subject".to_string()),
}
}
}
impl DesiredRole {
pub fn account<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::ForgeAccount>,
T::Error: ::std::fmt::Display,
{
self.account = value
.try_into()
.map_err(|e| format!("error converting supplied value for account: {e}"));
self
}
pub fn right<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::Right>,
T::Error: ::std::fmt::Display,
{
self.right = value
.try_into()
.map_err(|e| format!("error converting supplied value for right: {e}"));
self
}
pub fn subject<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::Did>,
T::Error: ::std::fmt::Display,
{
self.subject = value
.try_into()
.map_err(|e| format!("error converting supplied value for subject: {e}"));
self
}
}
impl ::std::convert::TryFrom<DesiredRole> for super::DesiredRole {
type Error = super::error::ConversionError;
fn try_from(
value: DesiredRole,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
account: value.account?,
right: value.right?,
subject: value.subject?,
})
}
}
impl ::std::convert::From<super::DesiredRole> for DesiredRole {
fn from(value: super::DesiredRole) -> Self {
Self {
account: Ok(value.account),
right: Ok(value.right),
subject: Ok(value.subject),
}
}
}
#[derive(Clone, Debug)]
pub struct ForgeAccount {
forge: ::std::result::Result<super::ForgeHost, ::std::string::String>,
id: ::std::result::Result<super::ForgeId, ::std::string::String>,
login: ::std::result::Result<super::ForgeAccountLogin, ::std::string::String>,
}
impl ::std::default::Default for ForgeAccount {
fn default() -> Self {
Self {
forge: Err("no value supplied for forge".to_string()),
id: Err("no value supplied for id".to_string()),
login: Err("no value supplied for login".to_string()),
}
}
}
impl ForgeAccount {
pub fn forge<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::ForgeHost>,
T::Error: ::std::fmt::Display,
{
self.forge = value
.try_into()
.map_err(|e| format!("error converting supplied value for forge: {e}"));
self
}
pub fn id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::ForgeId>,
T::Error: ::std::fmt::Display,
{
self.id = value
.try_into()
.map_err(|e| format!("error converting supplied value for id: {e}"));
self
}
pub fn login<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::ForgeAccountLogin>,
T::Error: ::std::fmt::Display,
{
self.login = value
.try_into()
.map_err(|e| format!("error converting supplied value for login: {e}"));
self
}
}
impl ::std::convert::TryFrom<ForgeAccount> for super::ForgeAccount {
type Error = super::error::ConversionError;
fn try_from(
value: ForgeAccount,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
forge: value.forge?,
id: value.id?,
login: value.login?,
})
}
}
impl ::std::convert::From<super::ForgeAccount> for ForgeAccount {
fn from(value: super::ForgeAccount) -> Self {
Self {
forge: Ok(value.forge),
id: Ok(value.id),
login: Ok(value.login),
}
}
}
#[derive(Clone, Debug)]
pub struct Payload {
desired_roles: ::std::result::Result<
::std::option::Option<Vec<super::DesiredRole>>,
::std::string::String,
>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
job_id: ::std::result::Result<super::JobId, ::std::string::String>,
kind: ::std::result::Result<super::PayloadKind, ::std::string::String>,
namespace: ::std::result::Result<super::NamespaceId, ::std::string::String>,
remove_accounts: ::std::result::Result<
::std::option::Option<Vec<super::ForgeAccount>>,
::std::string::String,
>,
repo: ::std::result::Result<
::std::option::Option<super::RepoResource>,
::std::string::String,
>,
spec: ::std::result::Result<::std::option::Option<super::RepoSpec>, ::std::string::String>,
steps: ::std::result::Result<
::std::option::Option<Vec<super::StepName>>,
::std::string::String,
>,
subject: ::std::result::Result<::std::option::Option<super::Did>, ::std::string::String>,
target:
::std::result::Result<::std::option::Option<super::BindTarget>, ::std::string::String>,
}
impl ::std::default::Default for Payload {
fn default() -> Self {
Self {
desired_roles: Ok(Default::default()),
ext: Ok(Default::default()),
job_id: Err("no value supplied for job_id".to_string()),
kind: Err("no value supplied for kind".to_string()),
namespace: Err("no value supplied for namespace".to_string()),
remove_accounts: Ok(Default::default()),
repo: Ok(Default::default()),
spec: Ok(Default::default()),
steps: Ok(Default::default()),
subject: Ok(Default::default()),
target: Ok(Default::default()),
}
}
}
impl Payload {
pub fn desired_roles<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<Vec<super::DesiredRole>>>,
T::Error: ::std::fmt::Display,
{
self.desired_roles = value
.try_into()
.map_err(|e| format!("error converting supplied value for desired_roles: {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 job_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::JobId>,
T::Error: ::std::fmt::Display,
{
self.job_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for job_id: {e}"));
self
}
pub fn kind<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::PayloadKind>,
T::Error: ::std::fmt::Display,
{
self.kind = value
.try_into()
.map_err(|e| format!("error converting supplied value for kind: {e}"));
self
}
pub fn namespace<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::NamespaceId>,
T::Error: ::std::fmt::Display,
{
self.namespace = value
.try_into()
.map_err(|e| format!("error converting supplied value for namespace: {e}"));
self
}
pub fn remove_accounts<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<Vec<super::ForgeAccount>>>,
T::Error: ::std::fmt::Display,
{
self.remove_accounts = value
.try_into()
.map_err(|e| format!("error converting supplied value for remove_accounts: {e}"));
self
}
pub fn repo<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::RepoResource>>,
T::Error: ::std::fmt::Display,
{
self.repo = value
.try_into()
.map_err(|e| format!("error converting supplied value for repo: {e}"));
self
}
pub fn spec<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::RepoSpec>>,
T::Error: ::std::fmt::Display,
{
self.spec = value
.try_into()
.map_err(|e| format!("error converting supplied value for spec: {e}"));
self
}
pub fn steps<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<Vec<super::StepName>>>,
T::Error: ::std::fmt::Display,
{
self.steps = value
.try_into()
.map_err(|e| format!("error converting supplied value for steps: {e}"));
self
}
pub fn subject<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::Did>>,
T::Error: ::std::fmt::Display,
{
self.subject = value
.try_into()
.map_err(|e| format!("error converting supplied value for subject: {e}"));
self
}
pub fn target<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::BindTarget>>,
T::Error: ::std::fmt::Display,
{
self.target = value
.try_into()
.map_err(|e| format!("error converting supplied value for target: {e}"));
self
}
}
impl ::std::convert::TryFrom<Payload> for super::Payload {
type Error = super::error::ConversionError;
fn try_from(value: Payload) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
desired_roles: value.desired_roles?,
ext: value.ext?,
job_id: value.job_id?,
kind: value.kind?,
namespace: value.namespace?,
remove_accounts: value.remove_accounts?,
repo: value.repo?,
spec: value.spec?,
steps: value.steps?,
subject: value.subject?,
target: value.target?,
})
}
}
impl ::std::convert::From<super::Payload> for Payload {
fn from(value: super::Payload) -> Self {
Self {
desired_roles: Ok(value.desired_roles),
ext: Ok(value.ext),
job_id: Ok(value.job_id),
kind: Ok(value.kind),
namespace: Ok(value.namespace),
remove_accounts: Ok(value.remove_accounts),
repo: Ok(value.repo),
spec: Ok(value.spec),
steps: Ok(value.steps),
subject: Ok(value.subject),
target: Ok(value.target),
}
}
}
#[derive(Clone, Debug)]
pub struct RepoSpec {
description: ::std::result::Result<
::std::option::Option<super::RepoSpecDescription>,
::std::string::String,
>,
visibility: ::std::result::Result<super::RepoVisibility, ::std::string::String>,
}
impl ::std::default::Default for RepoSpec {
fn default() -> Self {
Self {
description: Ok(Default::default()),
visibility: Err("no value supplied for visibility".to_string()),
}
}
}
impl RepoSpec {
pub fn description<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::RepoSpecDescription>>,
T::Error: ::std::fmt::Display,
{
self.description = value
.try_into()
.map_err(|e| format!("error converting supplied value for description: {e}"));
self
}
pub fn visibility<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::RepoVisibility>,
T::Error: ::std::fmt::Display,
{
self.visibility = value
.try_into()
.map_err(|e| format!("error converting supplied value for visibility: {e}"));
self
}
}
impl ::std::convert::TryFrom<RepoSpec> for super::RepoSpec {
type Error = super::error::ConversionError;
fn try_from(value: RepoSpec) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
description: value.description?,
visibility: value.visibility?,
})
}
}
impl ::std::convert::From<super::RepoSpec> for RepoSpec {
fn from(value: super::RepoSpec) -> Self {
Self {
description: Ok(value.description),
visibility: Ok(value.visibility),
}
}
}
#[derive(Clone, Debug)]
pub struct Response {
accepted: ::std::result::Result<bool, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
job_id: ::std::result::Result<super::JobId, ::std::string::String>,
next: ::std::result::Result<
::std::option::Option<super::ResponseNext>,
::std::string::String,
>,
}
impl ::std::default::Default for Response {
fn default() -> Self {
Self {
accepted: Err("no value supplied for accepted".to_string()),
ext: Ok(Default::default()),
job_id: Err("no value supplied for job_id".to_string()),
next: Ok(Default::default()),
}
}
}
impl Response {
pub fn accepted<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<bool>,
T::Error: ::std::fmt::Display,
{
self.accepted = value
.try_into()
.map_err(|e| format!("error converting supplied value for accepted: {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 job_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::JobId>,
T::Error: ::std::fmt::Display,
{
self.job_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for job_id: {e}"));
self
}
pub fn next<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::ResponseNext>>,
T::Error: ::std::fmt::Display,
{
self.next = value
.try_into()
.map_err(|e| format!("error converting supplied value for next: {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 {
accepted: value.accepted?,
ext: value.ext?,
job_id: value.job_id?,
next: value.next?,
})
}
}
impl ::std::convert::From<super::Response> for Response {
fn from(value: super::Response) -> Self {
Self {
accepted: Ok(value.accepted),
ext: Ok(value.ext),
job_id: Ok(value.job_id),
next: Ok(value.next),
}
}
}
#[derive(Clone, Debug)]
pub struct ResponseNext {
expires_at:
::std::result::Result<::chrono::DateTime<::chrono::offset::Utc>, ::std::string::String>,
url: ::std::result::Result<::std::string::String, ::std::string::String>,
user_code: ::std::result::Result<
::std::option::Option<super::ResponseNextUserCode>,
::std::string::String,
>,
}
impl ::std::default::Default for ResponseNext {
fn default() -> Self {
Self {
expires_at: Err("no value supplied for expires_at".to_string()),
url: Err("no value supplied for url".to_string()),
user_code: Ok(Default::default()),
}
}
}
impl ResponseNext {
pub fn expires_at<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::chrono::DateTime<::chrono::offset::Utc>>,
T::Error: ::std::fmt::Display,
{
self.expires_at = value
.try_into()
.map_err(|e| format!("error converting supplied value for expires_at: {e}"));
self
}
pub fn url<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::string::String>,
T::Error: ::std::fmt::Display,
{
self.url = value
.try_into()
.map_err(|e| format!("error converting supplied value for url: {e}"));
self
}
pub fn user_code<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::ResponseNextUserCode>>,
T::Error: ::std::fmt::Display,
{
self.user_code = value
.try_into()
.map_err(|e| format!("error converting supplied value for user_code: {e}"));
self
}
}
impl ::std::convert::TryFrom<ResponseNext> for super::ResponseNext {
type Error = super::error::ConversionError;
fn try_from(
value: ResponseNext,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
expires_at: value.expires_at?,
url: value.url?,
user_code: value.user_code?,
})
}
}
impl ::std::convert::From<super::ResponseNext> for ResponseNext {
fn from(value: super::ResponseNext) -> Self {
Self {
expires_at: Ok(value.expires_at),
url: Ok(value.url),
user_code: Ok(value.user_code),
}
}
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/git-ns/bridge/job/0.2";
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 \"BindTarget\": {\n \"additionalProperties\": false,\n \"description\": \"The forge owner whose control the binding admin is to prove.\",\n \"properties\": {\n \"forge\": {\n \"$ref\": \"#/$defs/ForgeHost\"\n },\n \"owner\": {\n \"$ref\": \"#/$defs/Segment\"\n }\n },\n \"required\": [\n \"forge\",\n \"owner\"\n ],\n \"title\": \"BindTarget\",\n \"type\": \"object\"\n },\n \"DesiredRole\": {\n \"additionalProperties\": false,\n \"properties\": {\n \"account\": {\n \"$ref\": \"#/$defs/ForgeAccount\",\n \"description\": \"The subject's linked account on this namespace's forge.\"\n },\n \"right\": {\n \"$ref\": \"#/$defs/Right\",\n \"description\": \"The subject's highest effective right on the target, which the forge adapter maps to one of its roles.\"\n },\n \"subject\": {\n \"$ref\": \"#/$defs/Did\"\n }\n },\n \"required\": [\n \"subject\",\n \"account\",\n \"right\"\n ],\n \"type\": \"object\"\n },\n \"Did\": {\n \"description\": \"A DID, compared by exact string equality.\",\n \"maxLength\": 2048,\n \"pattern\": \"^did:[a-z0-9]+:\\\\S+$\",\n \"title\": \"Did\",\n \"type\": \"string\"\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 \"ForgeAccount\": {\n \"additionalProperties\": false,\n \"description\": \"A person's account on one forge. `id` is authoritative; `login` is for display only, because logins can be renamed and re-registered.\",\n \"properties\": {\n \"forge\": {\n \"$ref\": \"#/$defs/ForgeHost\"\n },\n \"id\": {\n \"$ref\": \"#/$defs/ForgeId\"\n },\n \"login\": {\n \"description\": \"The account's current login, as the forge reported it when last seen. Display only.\",\n \"maxLength\": 100,\n \"minLength\": 1,\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"forge\",\n \"id\",\n \"login\"\n ],\n \"title\": \"ForgeAccount\",\n \"type\": \"object\"\n },\n \"ForgeHost\": {\n \"description\": \"The lowercased DNS host of a forge: `github.com`, a GitHub Enterprise Server host, `codeberg.org`, or a self-hosted Forgejo instance such as `git.example.org`. No scheme, no port, no path. The host is a segment of every resource, so a right never crosses forges.\",\n \"maxLength\": 253,\n \"minLength\": 3,\n \"pattern\": \"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$\",\n \"title\": \"ForgeHost\",\n \"type\": \"string\"\n },\n \"ForgeId\": {\n \"description\": \"An identifier the forge itself assigns — a repository id, a user or organisation id — carried as a string so a forge whose ids are not numbers needs no new version. GitHub and Forgejo ids are decimal integers written as strings (`\\\"812736451\\\"`). Unlike a name, it survives renames and transfers, which is why rights and bindings are keyed by it.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"title\": \"ForgeId\",\n \"type\": \"string\"\n },\n \"JobId\": {\n \"description\": \"The VTC's identifier for a job, unique across the VTC's jobs.\",\n \"maxLength\": 128,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"NamespaceId\": {\n \"description\": \"The VTC's opaque identifier for a namespace, assigned when it is bound. Stable for the life of the binding; never reused for another binding.\",\n \"maxLength\": 128,\n \"minLength\": 1,\n \"title\": \"NamespaceId\",\n \"type\": \"string\"\n },\n \"RepoResource\": {\n \"description\": \"A forge-qualified resource naming exactly one repository: `<forge-host>/<owner>/<repo>`, lowercase.\",\n \"maxLength\": 455,\n \"pattern\": \"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+/[a-z0-9_-][a-z0-9._-]{0,99}/[a-z0-9_-][a-z0-9._-]{0,99}$\",\n \"title\": \"RepoResource\",\n \"type\": \"string\"\n },\n \"RepoSpec\": {\n \"additionalProperties\": false,\n \"properties\": {\n \"description\": {\n \"maxLength\": 350,\n \"type\": \"string\"\n },\n \"visibility\": {\n \"$ref\": \"#/$defs/RepoVisibility\"\n }\n },\n \"required\": [\n \"visibility\"\n ],\n \"type\": \"object\"\n },\n \"RepoVisibility\": {\n \"description\": \"Repository visibility on the forge.\",\n \"enum\": [\n \"public\",\n \"private\"\n ],\n \"title\": \"RepoVisibility\",\n \"type\": \"string\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"description\": \"Whether the bridge took the job.\",\n \"properties\": {\n \"accepted\": {\n \"description\": \"`true` — the job is durably queued, or was already queued or running under this `jobId`; a git-ns/bridge/result follows. `false` — the bridge already finished this `jobId`; it does not run it again, and sends its git-ns/bridge/result again.\",\n \"type\": \"boolean\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"jobId\": {\n \"$ref\": \"#/$defs/JobId\"\n },\n \"next\": {\n \"additionalProperties\": false,\n \"description\": \"For `beginBind` and `beginAccountLink` when `accepted` is `true`: where the VTC sends the person.\",\n \"properties\": {\n \"expiresAt\": {\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"url\": {\n \"format\": \"uri\",\n \"maxLength\": 2048,\n \"pattern\": \"^https://\",\n \"type\": \"string\"\n },\n \"userCode\": {\n \"description\": \"The device-flow code, for a forge that uses one.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"url\",\n \"expiresAt\"\n ],\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"jobId\",\n \"accepted\"\n ],\n \"title\": \"Git Namespaces — Bridge Job — response payload\",\n \"type\": \"object\"\n },\n \"Right\": {\n \"description\": \"One of the five git rights. Each string is also the TRQP `action` the VTC publishes the right under in its Trust Registry, so it is carried verbatim. `git.ns.admin` and `git.repo.create` apply to a namespace resource; `git.repo.own` and `git.repo.maintain` to a repository resource; `git.commit.sign` to either.\",\n \"enum\": [\n \"git.ns.admin\",\n \"git.repo.create\",\n \"git.repo.own\",\n \"git.repo.maintain\",\n \"git.commit.sign\"\n ],\n \"title\": \"Right\",\n \"type\": \"string\"\n },\n \"Segment\": {\n \"description\": \"One lowercased owner or repository name. Forges compare these case-insensitively, so the wire form is always lowercase and a producer lowercases before sending. A leading `.` is refused, which rules out `.` and `..`.\",\n \"maxLength\": 100,\n \"minLength\": 1,\n \"pattern\": \"^[a-z0-9_-][a-z0-9._-]*$\",\n \"title\": \"Segment\",\n \"type\": \"string\"\n },\n \"StepName\": {\n \"description\": \"One step of a forge adapter's plan. Forge-neutral names: `create`, `workflow`, `keyring`, `variables`, `requiredCheck`, `roles`, `archive`. An adapter MAY add its own (`mergeStyle`, `runner`), which a VTC displays but need not understand.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"pattern\": \"^[a-z][A-Za-z0-9]*$\",\n \"type\": \"string\"\n }\n },\n \"$id\": \"https://trusttasks.org/spec/git-ns/bridge/job/0.2\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"description\": \"The VTC asks its bridge to do one piece of forge work in a namespace. The job names what to do (`kind`) and carries the desired state, never forge credentials — the bridge holds those. Which optional members a job carries is fixed by its `kind` (see the specification's kind table); a bridge refuses a job that carries a member its kind does not use, or lacks one its kind requires.\",\n \"properties\": {\n \"desiredRoles\": {\n \"description\": \"The complete set of people who should hold a forge role on the target. The bridge converges to exactly this set among the roles it manages: it adds or changes roles to match and removes managed roles not listed. `projectRoles` and `createRepo` only.\",\n \"items\": {\n \"$ref\": \"#/$defs/DesiredRole\"\n },\n \"type\": \"array\",\n \"uniqueItems\": true\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"jobId\": {\n \"$ref\": \"#/$defs/JobId\",\n \"description\": \"The VTC's identifier for this job. The bridge reports on it with git-ns/bridge/result.\"\n },\n \"kind\": {\n \"description\": \"What to do. `projectRoles` — converge forge roles to `desiredRoles`, and remove whatever role each of `removeAccounts` holds on `repo`. `createRepo` — create `repo`, run the bootstrap plan, and project `desiredRoles`. `bootstrap` — run the bootstrap plan, or the listed `steps` of it, on an existing `repo`. `archive` — archive `repo`. `inspect` — compare the forge with the projection for `repo`, or for the whole namespace. `beginBind` — start the proof that the binding admin controls `target`. `beginAccountLink` — start linking `subject`'s account on this namespace's forge.\",\n \"enum\": [\n \"projectRoles\",\n \"createRepo\",\n \"bootstrap\",\n \"archive\",\n \"inspect\",\n \"beginBind\",\n \"beginAccountLink\"\n ],\n \"type\": \"string\"\n },\n \"namespace\": {\n \"$ref\": \"#/$defs/NamespaceId\",\n \"description\": \"The namespace the job acts in. It also selects the bridge's forge credentials for it.\"\n },\n \"removeAccounts\": {\n \"description\": \"Accounts whose direct role on `repo` the bridge removes, whatever that role is — including a role the bridge does not manage and would otherwise only report as drift. Matched by `forge` and `id`; `login` is display only. No account listed here may also appear in `desiredRoles`. `projectRoles` with `repo` only.\",\n \"items\": {\n \"$ref\": \"#/$defs/ForgeAccount\"\n },\n \"minItems\": 1,\n \"type\": \"array\",\n \"uniqueItems\": true\n },\n \"repo\": {\n \"$ref\": \"#/$defs/RepoResource\",\n \"description\": \"The repository. Required for `createRepo`, `bootstrap` and `archive`. For `projectRoles`, absent means the namespace-level roles (organisation owners), where only `git.ns.admin` is projected. For `inspect`, absent means a sweep of every repository in the namespace.\"\n },\n \"spec\": {\n \"$ref\": \"#/$defs/RepoSpec\",\n \"description\": \"How to create the repository. `createRepo` only.\"\n },\n \"steps\": {\n \"description\": \"Run only these steps of the adapter's bootstrap plan, in the plan's order. Absent: the whole plan. `bootstrap` only.\",\n \"items\": {\n \"$ref\": \"#/$defs/StepName\"\n },\n \"minItems\": 1,\n \"type\": \"array\",\n \"uniqueItems\": true\n },\n \"subject\": {\n \"$ref\": \"#/$defs/Did\",\n \"description\": \"The member whose forge account is being linked. `beginAccountLink` only.\"\n },\n \"target\": {\n \"$ref\": \"#/$defs/BindTarget\",\n \"description\": \"`beginBind` only.\"\n }\n },\n \"required\": [\n \"jobId\",\n \"namespace\",\n \"kind\"\n ],\n \"title\": \"Git Namespaces — Bridge Job — payload\",\n \"type\": \"object\"\n}\n",
);
}
impl crate::Payload for Response {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/git-ns/bridge/job/0.2#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 \"BindTarget\": {\n \"additionalProperties\": false,\n \"description\": \"The forge owner whose control the binding admin is to prove.\",\n \"properties\": {\n \"forge\": {\n \"$ref\": \"#/$defs/ForgeHost\"\n },\n \"owner\": {\n \"$ref\": \"#/$defs/Segment\"\n }\n },\n \"required\": [\n \"forge\",\n \"owner\"\n ],\n \"title\": \"BindTarget\",\n \"type\": \"object\"\n },\n \"DesiredRole\": {\n \"additionalProperties\": false,\n \"properties\": {\n \"account\": {\n \"$ref\": \"#/$defs/ForgeAccount\",\n \"description\": \"The subject's linked account on this namespace's forge.\"\n },\n \"right\": {\n \"$ref\": \"#/$defs/Right\",\n \"description\": \"The subject's highest effective right on the target, which the forge adapter maps to one of its roles.\"\n },\n \"subject\": {\n \"$ref\": \"#/$defs/Did\"\n }\n },\n \"required\": [\n \"subject\",\n \"account\",\n \"right\"\n ],\n \"type\": \"object\"\n },\n \"Did\": {\n \"description\": \"A DID, compared by exact string equality.\",\n \"maxLength\": 2048,\n \"pattern\": \"^did:[a-z0-9]+:\\\\S+$\",\n \"title\": \"Did\",\n \"type\": \"string\"\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 \"ForgeAccount\": {\n \"additionalProperties\": false,\n \"description\": \"A person's account on one forge. `id` is authoritative; `login` is for display only, because logins can be renamed and re-registered.\",\n \"properties\": {\n \"forge\": {\n \"$ref\": \"#/$defs/ForgeHost\"\n },\n \"id\": {\n \"$ref\": \"#/$defs/ForgeId\"\n },\n \"login\": {\n \"description\": \"The account's current login, as the forge reported it when last seen. Display only.\",\n \"maxLength\": 100,\n \"minLength\": 1,\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"forge\",\n \"id\",\n \"login\"\n ],\n \"title\": \"ForgeAccount\",\n \"type\": \"object\"\n },\n \"ForgeHost\": {\n \"description\": \"The lowercased DNS host of a forge: `github.com`, a GitHub Enterprise Server host, `codeberg.org`, or a self-hosted Forgejo instance such as `git.example.org`. No scheme, no port, no path. The host is a segment of every resource, so a right never crosses forges.\",\n \"maxLength\": 253,\n \"minLength\": 3,\n \"pattern\": \"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$\",\n \"title\": \"ForgeHost\",\n \"type\": \"string\"\n },\n \"ForgeId\": {\n \"description\": \"An identifier the forge itself assigns — a repository id, a user or organisation id — carried as a string so a forge whose ids are not numbers needs no new version. GitHub and Forgejo ids are decimal integers written as strings (`\\\"812736451\\\"`). Unlike a name, it survives renames and transfers, which is why rights and bindings are keyed by it.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"title\": \"ForgeId\",\n \"type\": \"string\"\n },\n \"JobId\": {\n \"description\": \"The VTC's identifier for a job, unique across the VTC's jobs.\",\n \"maxLength\": 128,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"NamespaceId\": {\n \"description\": \"The VTC's opaque identifier for a namespace, assigned when it is bound. Stable for the life of the binding; never reused for another binding.\",\n \"maxLength\": 128,\n \"minLength\": 1,\n \"title\": \"NamespaceId\",\n \"type\": \"string\"\n },\n \"RepoResource\": {\n \"description\": \"A forge-qualified resource naming exactly one repository: `<forge-host>/<owner>/<repo>`, lowercase.\",\n \"maxLength\": 455,\n \"pattern\": \"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+/[a-z0-9_-][a-z0-9._-]{0,99}/[a-z0-9_-][a-z0-9._-]{0,99}$\",\n \"title\": \"RepoResource\",\n \"type\": \"string\"\n },\n \"RepoSpec\": {\n \"additionalProperties\": false,\n \"properties\": {\n \"description\": {\n \"maxLength\": 350,\n \"type\": \"string\"\n },\n \"visibility\": {\n \"$ref\": \"#/$defs/RepoVisibility\"\n }\n },\n \"required\": [\n \"visibility\"\n ],\n \"type\": \"object\"\n },\n \"RepoVisibility\": {\n \"description\": \"Repository visibility on the forge.\",\n \"enum\": [\n \"public\",\n \"private\"\n ],\n \"title\": \"RepoVisibility\",\n \"type\": \"string\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"description\": \"Whether the bridge took the job.\",\n \"properties\": {\n \"accepted\": {\n \"description\": \"`true` — the job is durably queued, or was already queued or running under this `jobId`; a git-ns/bridge/result follows. `false` — the bridge already finished this `jobId`; it does not run it again, and sends its git-ns/bridge/result again.\",\n \"type\": \"boolean\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"jobId\": {\n \"$ref\": \"#/$defs/JobId\"\n },\n \"next\": {\n \"additionalProperties\": false,\n \"description\": \"For `beginBind` and `beginAccountLink` when `accepted` is `true`: where the VTC sends the person.\",\n \"properties\": {\n \"expiresAt\": {\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"url\": {\n \"format\": \"uri\",\n \"maxLength\": 2048,\n \"pattern\": \"^https://\",\n \"type\": \"string\"\n },\n \"userCode\": {\n \"description\": \"The device-flow code, for a forge that uses one.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"url\",\n \"expiresAt\"\n ],\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"jobId\",\n \"accepted\"\n ],\n \"title\": \"Git Namespaces — Bridge Job — response payload\",\n \"type\": \"object\"\n },\n \"Right\": {\n \"description\": \"One of the five git rights. Each string is also the TRQP `action` the VTC publishes the right under in its Trust Registry, so it is carried verbatim. `git.ns.admin` and `git.repo.create` apply to a namespace resource; `git.repo.own` and `git.repo.maintain` to a repository resource; `git.commit.sign` to either.\",\n \"enum\": [\n \"git.ns.admin\",\n \"git.repo.create\",\n \"git.repo.own\",\n \"git.repo.maintain\",\n \"git.commit.sign\"\n ],\n \"title\": \"Right\",\n \"type\": \"string\"\n },\n \"Segment\": {\n \"description\": \"One lowercased owner or repository name. Forges compare these case-insensitively, so the wire form is always lowercase and a producer lowercases before sending. A leading `.` is refused, which rules out `.` and `..`.\",\n \"maxLength\": 100,\n \"minLength\": 1,\n \"pattern\": \"^[a-z0-9_-][a-z0-9._-]*$\",\n \"title\": \"Segment\",\n \"type\": \"string\"\n },\n \"StepName\": {\n \"description\": \"One step of a forge adapter's plan. Forge-neutral names: `create`, `workflow`, `keyring`, `variables`, `requiredCheck`, `roles`, `archive`. An adapter MAY add its own (`mergeStyle`, `runner`), which a VTC displays but need not understand.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"pattern\": \"^[a-z][A-Za-z0-9]*$\",\n \"type\": \"string\"\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::UNKNOWN_NAMESPACE,
error_codes::NOT_CAPABLE,
error_codes::JOB_ID_REUSED,
];
/// 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 {
/// `git-ns:unknownNamespace`
///
/// No namespace bound to this VTC has this identifier, or contains this resource.
///
/// Declared `retryable: false`.
pub const UNKNOWN_NAMESPACE: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "git-ns:unknownNamespace",
retryable: false,
};
/// `git-ns/bridge/job:notCapable`
///
/// This forge, or this namespace on it, cannot perform this kind of job — for example `createRepo` on a personal account.
///
/// Declared `retryable: false`.
pub const NOT_CAPABLE: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "git-ns/bridge/job:notCapable",
retryable: false,
};
/// `git-ns/bridge/job:jobIdReused`
///
/// The bridge already holds a job with this `jobId` and different content.
///
/// Declared `retryable: false`.
pub const JOB_ID_REUSED: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "git-ns/bridge/job:jobIdReused",
retryable: false,
};
}
#[cfg(test)]
mod conformance {
//! Round-trip tests harvested from the spec's `spec.md`,
//! plus a `rejects_invalid_examples` test for any fixtures
//! in `payload.invalid-examples.json` (validate feature).
#[test]
fn request_example_1() {
const JSON: &str = "{\n \"id\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d01\",\n \"type\": \"https://trusttasks.org/spec/git-ns/bridge/job/0.2\",\n \"threadId\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d01\",\n \"issuer\": \"did:webvh:QmVtcScid7:acme-vtc.example\",\n \"recipient\": \"did:webvh:QmBridgeScid5:bridge.acme-vtc.example\",\n \"issuedAt\": \"2026-09-23T10:00:00Z\",\n \"payload\": {\n \"jobId\": \"job_01J8ZA3K7F\",\n \"namespace\": \"ns_01J8Z6Q4M2\",\n \"kind\": \"createRepo\",\n \"repo\": \"github.com/acme/gadgets\",\n \"spec\": {\n \"visibility\": \"public\",\n \"description\": \"Small tools for widgets\"\n },\n \"desiredRoles\": [\n {\n \"subject\": \"did:webvh:QmBobScid2:acme-vtc.example:bob\",\n \"account\": {\n \"forge\": \"github.com\",\n \"id\": \"9120045\",\n \"login\": \"bob-builds\"\n },\n \"right\": \"git.repo.own\"\n }\n ]\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"did:webvh:QmVtcScid7:acme-vtc.example#key-1\",\n \"created\": \"2026-09-23T10:00:00Z\",\n \"proofPurpose\": \"authentication\",\n \"proofValue\": \"zk5f5NdC3fyVGTjHt3wAfoua65baH7EMsYGngmCMHUp8sV5gfHLq5eCqkQi6wZn3HAfnsE8Rzu9XAaaK4nxDHRy\"\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_2() {
const JSON: &str = "{\n \"id\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d03\",\n \"type\": \"https://trusttasks.org/spec/git-ns/bridge/job/0.2\",\n \"threadId\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d03\",\n \"issuer\": \"did:webvh:QmVtcScid7:acme-vtc.example\",\n \"recipient\": \"did:webvh:QmBridgeScid5:bridge.acme-vtc.example\",\n \"issuedAt\": \"2026-09-23T10:00:00Z\",\n \"payload\": {\n \"jobId\": \"job_01J8ZB0P2C\",\n \"namespace\": \"ns_01J8Z6Q4M2\",\n \"kind\": \"beginAccountLink\",\n \"subject\": \"did:webvh:QmBobScid2:acme-vtc.example:bob\"\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"did:webvh:QmVtcScid7:acme-vtc.example#key-1\",\n \"created\": \"2026-09-23T10:00:00Z\",\n \"proofPurpose\": \"authentication\",\n \"proofValue\": \"zsMQG7URHk2DRiFd9kuB5WojM61NFSJj7eLqEjFLvuU4LTcw2FSegdznkN8yDuRtdrVo1twjjPb4Ndfzh3sTqBV\"\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\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d07\",\n \"type\": \"https://trusttasks.org/spec/git-ns/bridge/job/0.2\",\n \"threadId\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d07\",\n \"issuer\": \"did:webvh:QmVtcScid7:acme-vtc.example\",\n \"recipient\": \"did:webvh:QmBridgeScid5:bridge.acme-vtc.example\",\n \"issuedAt\": \"2026-09-24T09:10:00Z\",\n \"payload\": {\n \"jobId\": \"job_01J8ZF2R6W\",\n \"namespace\": \"ns_01J8Z6Q4M2\",\n \"kind\": \"projectRoles\",\n \"repo\": \"github.com/acme/widgets\",\n \"desiredRoles\": [\n {\n \"subject\": \"did:webvh:QmAliceScid1:acme-vtc.example:alice\",\n \"account\": {\n \"forge\": \"github.com\",\n \"id\": \"4410987\",\n \"login\": \"alice-acme\"\n },\n \"right\": \"git.repo.own\"\n },\n {\n \"subject\": \"did:webvh:QmCarolScid3:acme-vtc.example:carol\",\n \"account\": {\n \"forge\": \"github.com\",\n \"id\": \"7781202\",\n \"login\": \"carol-c\"\n },\n \"right\": \"git.repo.own\"\n }\n ],\n \"removeAccounts\": [\n {\n \"forge\": \"github.com\",\n \"id\": \"5550123\",\n \"login\": \"eve-dev\"\n }\n ]\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"did:webvh:QmVtcScid7:acme-vtc.example#key-1\",\n \"created\": \"2026-09-24T09:10:00Z\",\n \"proofPurpose\": \"authentication\",\n \"proofValue\": \"z3sQm1bXv9Hk2TfWcN8rJ4pLdE6yG7uA5oKiZ2qRx1VnB8cM3wF9tS4hD6jP2eL7gU5aY1kQ3rT8mW4nX9vC2bZ\"\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_4() {
const JSON: &str = "{\n \"id\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d05\",\n \"type\": \"https://trusttasks.org/spec/git-ns/bridge/job/0.2\",\n \"threadId\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d05\",\n \"issuer\": \"did:webvh:QmVtcScid7:acme-vtc.example\",\n \"recipient\": \"did:webvh:QmBridgeScid5:bridge.acme-vtc.example\",\n \"issuedAt\": \"2026-09-23T10:00:00Z\",\n \"payload\": {\n \"jobId\": \"job_01J8ZC9D4H\",\n \"namespace\": \"ns_01J8Z6Q4M2\",\n \"kind\": \"bootstrap\",\n \"repo\": \"github.com/acme/widgets\",\n \"steps\": [\n \"requiredCheck\"\n ]\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"did:webvh:QmVtcScid7:acme-vtc.example#key-1\",\n \"created\": \"2026-09-23T10:00:00Z\",\n \"proofPurpose\": \"authentication\",\n \"proofValue\": \"zfNBCVyyxc4YJ4LBc3MC4LswfMaAVgwC7Pv5mW2ZAnXCCP84g3KpfQX3UjZL63ovm2257Fbk78GdZNt2rZTCDPF\"\n }\n}\n";
let doc: crate::TrustTask<super::Payload> =
serde_json::from_str(JSON).expect("deserialize request example");
let rendered = serde_json::to_value(&doc).expect("re-serialize");
let expected: serde_json::Value = serde_json::from_str(JSON).expect("re-parse expected");
assert_eq!(rendered, expected, "request example failed round-trip");
}
#[test]
fn response_example_1() {
const JSON: &str = "{\n \"id\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d02\",\n \"type\": \"https://trusttasks.org/spec/git-ns/bridge/job/0.2#response\",\n \"threadId\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d01\",\n \"issuer\": \"did:webvh:QmBridgeScid5:bridge.acme-vtc.example\",\n \"recipient\": \"did:webvh:QmVtcScid7:acme-vtc.example\",\n \"issuedAt\": \"2026-09-23T10:00:01Z\",\n \"payload\": {\n \"jobId\": \"job_01J8ZA3K7F\",\n \"accepted\": true\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"did:webvh:QmBridgeScid5:bridge.acme-vtc.example#key-1\",\n \"created\": \"2026-09-23T10:00:01Z\",\n \"proofPurpose\": \"authentication\",\n \"proofValue\": \"zB6QrBqXENi973GjjCeVjVVUUDempgtCdj4GY4ztNUpL2ZvJv97ZVmZo66EkCYFZbu1YdaWPJnTj42jJ57qx6cv\"\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\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d04\",\n \"type\": \"https://trusttasks.org/spec/git-ns/bridge/job/0.2#response\",\n \"threadId\": \"urn:uuid:95dedda2-126b-419d-a9c5-5babd36f6d03\",\n \"issuer\": \"did:webvh:QmBridgeScid5:bridge.acme-vtc.example\",\n \"recipient\": \"did:webvh:QmVtcScid7:acme-vtc.example\",\n \"issuedAt\": \"2026-09-23T10:00:01Z\",\n \"payload\": {\n \"jobId\": \"job_01J8ZB0P2C\",\n \"accepted\": true,\n \"next\": {\n \"url\": \"https://github.com/login/device\",\n \"userCode\": \"WDJB-MJHT\",\n \"expiresAt\": \"2026-09-23T10:15:00Z\"\n }\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"did:webvh:QmBridgeScid5:bridge.acme-vtc.example#key-1\",\n \"created\": \"2026-09-23T10:00:01Z\",\n \"proofPurpose\": \"authentication\",\n \"proofValue\": \"z6xNLK38BZKGr3S86pfLF3E6y77dxH2i8Kv7mpPG7uVLBBDGgEac6CRC8T9UrHP9L8CSpUmp3yc3GETRsV2dB2N\"\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)] = &[
(
"`kind` is one of the seven forge-neutral kinds.",
"{\n \"jobId\": \"job_1\",\n \"kind\": \"deleteRepo\",\n \"namespace\": \"ns_1\",\n \"repo\": \"github.com/acme/widgets\"\n}",
),
(
"`jobId`, `namespace` and `kind` are always required.",
"{\n \"kind\": \"archive\",\n \"repo\": \"github.com/acme/widgets\"\n}",
),
(
"Jobs never carry forge credentials.",
"{\n \"jobId\": \"job_1\",\n \"kind\": \"archive\",\n \"namespace\": \"ns_1\",\n \"repo\": \"github.com/acme/widgets\",\n \"token\": \"ghs_example\"\n}",
),
(
"A desired role names a git right, not a forge role: mapping rights to roles is the adapter's job.",
"{\n \"desiredRoles\": [\n {\n \"account\": {\n \"forge\": \"github.com\",\n \"id\": \"9120045\",\n \"login\": \"bob-builds\"\n },\n \"right\": \"admin\",\n \"subject\": \"did:webvh:QmBobScid2:acme-vtc.example:bob\"\n }\n ],\n \"jobId\": \"job_1\",\n \"kind\": \"projectRoles\",\n \"namespace\": \"ns_1\",\n \"repo\": \"github.com/acme/widgets\"\n}",
),
(
"Step names are lowerCamelCase identifiers.",
"{\n \"jobId\": \"job_1\",\n \"kind\": \"bootstrap\",\n \"namespace\": \"ns_1\",\n \"repo\": \"github.com/acme/widgets\",\n \"steps\": [\n \"required-check\"\n ]\n}",
),
(
"`removeAccounts`, when present, names at least one account: an empty list would be a job that asks for nothing to be removed while looking as though it did.",
"{\n \"desiredRoles\": [],\n \"jobId\": \"job_1\",\n \"kind\": \"projectRoles\",\n \"namespace\": \"ns_1\",\n \"removeAccounts\": [],\n \"repo\": \"github.com/acme/widgets\"\n}",
),
(
"`removeAccounts` lists forge accounts, not DIDs: the account to take off the forge often belongs to someone with no DID the VTC knows.",
"{\n \"desiredRoles\": [],\n \"jobId\": \"job_1\",\n \"kind\": \"projectRoles\",\n \"namespace\": \"ns_1\",\n \"removeAccounts\": [\n \"did:webvh:QmDanScid4:dan.example\"\n ],\n \"repo\": \"github.com/acme/widgets\"\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
);
}
}
}