//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `git-ns/namespace/bind`. Version: `0.1`.
#[allow(unused_imports)]
use serde::{Deserialize, Serialize};
/// Error types.
pub mod error {
/// Error from a `TryFrom` or `FromStr` implementation.
pub struct ConversionError(::std::borrow::Cow<'static, str>);
impl ::std::error::Error for ConversionError {}
impl ::std::fmt::Display for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Display::fmt(&self.0, f)
}
}
impl ::std::fmt::Debug for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Debug::fmt(&self.0, f)
}
}
impl From<&'static str> for ConversionError {
fn from(value: &'static str) -> Self {
Self(value.into())
}
}
impl From<String> for ConversionError {
fn from(value: String) -> Self {
Self(value.into())
}
}
}
///Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Ext",
/// "description": "Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.",
/// "type": "object",
/// "minProperties": 1,
/// "additionalProperties": true,
/// "propertyNames": {
/// "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
/// }
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(transparent)]
pub struct Ext(pub ::std::collections::HashMap<ExtKey, ::serde_json::Value>);
impl ::std::ops::Deref for Ext {
type Target = ::std::collections::HashMap<ExtKey, ::serde_json::Value>;
fn deref(&self) -> &::std::collections::HashMap<ExtKey, ::serde_json::Value> {
&self.0
}
}
impl ::std::convert::From<Ext> for ::std::collections::HashMap<ExtKey, ::serde_json::Value> {
fn from(value: Ext) -> Self {
value.0
}
}
impl ::std::convert::From<::std::collections::HashMap<ExtKey, ::serde_json::Value>> for Ext {
fn from(value: ::std::collections::HashMap<ExtKey, ::serde_json::Value>) -> Self {
Self(value)
}
}
///`ExtKey`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ExtKey(::std::string::String);
impl ::std::ops::Deref for ExtKey {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ExtKey> for ::std::string::String {
fn from(value: ExtKey) -> Self {
value.0
}
}
impl ::std::str::FromStr for ExtKey {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new("^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$").unwrap()
});
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for ExtKey {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///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())
})
}
}
///The VTC's binding to one owner on one forge.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "GitNamespace",
/// "description": "The VTC's binding to one owner on one forge.",
/// "type": "object",
/// "required": [
/// "forge",
/// "id",
/// "mode",
/// "owner",
/// "state"
/// ],
/// "properties": {
/// "forge": {
/// "$ref": "#/definitions/ForgeHost"
/// },
/// "id": {
/// "$ref": "#/definitions/NamespaceId"
/// },
/// "kind": {
/// "description": "Whether the owner is an organisation or a personal account, as the forge reports it. Present once known: a bridge-mode namespace learns it when binding completes, and a manual-mode namespace MAY never learn it.",
/// "type": "string",
/// "enum": [
/// "organization",
/// "user"
/// ]
/// },
/// "mode": {
/// "description": "`bridge` — a bridge service acts on the forge for this namespace (creates repositories, projects roles, reports drift). `manual` — no automation; people with forge access carry out the steps the VTC names, and the VTC governs the rights alone.",
/// "type": "string",
/// "enum": [
/// "bridge",
/// "manual"
/// ]
/// },
/// "owner": {
/// "description": "The organisation or user on the forge, lowercased.",
/// "$ref": "#/definitions/Segment"
/// },
/// "state": {
/// "description": "`pending` — binding has started and the forge-side proof has not arrived yet. `bound` — the VTC governs rights under this namespace.",
/// "type": "string",
/// "enum": [
/// "pending",
/// "bound"
/// ]
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct GitNamespace {
pub forge: ForgeHost,
pub id: NamespaceId,
///Whether the owner is an organisation or a personal account, as the forge reports it. Present once known: a bridge-mode namespace learns it when binding completes, and a manual-mode namespace MAY never learn it.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub kind: ::std::option::Option<GitNamespaceKind>,
///`bridge` — a bridge service acts on the forge for this namespace (creates repositories, projects roles, reports drift). `manual` — no automation; people with forge access carry out the steps the VTC names, and the VTC governs the rights alone.
pub mode: GitNamespaceMode,
///The organisation or user on the forge, lowercased.
pub owner: Segment,
///`pending` — binding has started and the forge-side proof has not arrived yet. `bound` — the VTC governs rights under this namespace.
pub state: GitNamespaceState,
}
impl GitNamespace {
pub fn builder() -> builder::GitNamespace {
Default::default()
}
}
///Whether the owner is an organisation or a personal account, as the forge reports it. Present once known: a bridge-mode namespace learns it when binding completes, and a manual-mode namespace MAY never learn it.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Whether the owner is an organisation or a personal account, as the forge reports it. Present once known: a bridge-mode namespace learns it when binding completes, and a manual-mode namespace MAY never learn it.",
/// "type": "string",
/// "enum": [
/// "organization",
/// "user"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum GitNamespaceKind {
#[serde(rename = "organization")]
Organization,
#[serde(rename = "user")]
User,
}
impl ::std::fmt::Display for GitNamespaceKind {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Organization => f.write_str("organization"),
Self::User => f.write_str("user"),
}
}
}
impl ::std::str::FromStr for GitNamespaceKind {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"organization" => Ok(Self::Organization),
"user" => Ok(Self::User),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for GitNamespaceKind {
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 GitNamespaceKind {
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 GitNamespaceKind {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///`bridge` — a bridge service acts on the forge for this namespace (creates repositories, projects roles, reports drift). `manual` — no automation; people with forge access carry out the steps the VTC names, and the VTC governs the rights alone.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "`bridge` — a bridge service acts on the forge for this namespace (creates repositories, projects roles, reports drift). `manual` — no automation; people with forge access carry out the steps the VTC names, and the VTC governs the rights alone.",
/// "type": "string",
/// "enum": [
/// "bridge",
/// "manual"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum GitNamespaceMode {
#[serde(rename = "bridge")]
Bridge,
#[serde(rename = "manual")]
Manual,
}
impl ::std::fmt::Display for GitNamespaceMode {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Bridge => f.write_str("bridge"),
Self::Manual => f.write_str("manual"),
}
}
}
impl ::std::str::FromStr for GitNamespaceMode {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"bridge" => Ok(Self::Bridge),
"manual" => Ok(Self::Manual),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for GitNamespaceMode {
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 GitNamespaceMode {
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 GitNamespaceMode {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///`pending` — binding has started and the forge-side proof has not arrived yet. `bound` — the VTC governs rights under this namespace.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "`pending` — binding has started and the forge-side proof has not arrived yet. `bound` — the VTC governs rights under this namespace.",
/// "type": "string",
/// "enum": [
/// "pending",
/// "bound"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum GitNamespaceState {
#[serde(rename = "pending")]
Pending,
#[serde(rename = "bound")]
Bound,
}
impl ::std::fmt::Display for GitNamespaceState {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Pending => f.write_str("pending"),
Self::Bound => f.write_str("bound"),
}
}
}
impl ::std::str::FromStr for GitNamespaceState {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"pending" => Ok(Self::Pending),
"bound" => Ok(Self::Bound),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for GitNamespaceState {
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 GitNamespaceState {
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 GitNamespaceState {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///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())
})
}
}
///A VTC administrator binds the VTC to one owner on one forge, so that the VTC governs who may create, own, maintain and commit to repositories under it. In bridge mode the response says where to send the forge owner to prove control; the namespace stays `pending` until that proof arrives.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/git-ns/namespace/bind/0.1",
/// "title": "Payload",
/// "description": "A VTC administrator binds the VTC to one owner on one forge, so that the VTC governs who may create, own, maintain and commit to repositories under it. In bridge mode the response says where to send the forge owner to prove control; the namespace stays `pending` until that proof arrives.",
/// "type": "object",
/// "required": [
/// "forge",
/// "mode",
/// "owner"
/// ],
/// "properties": {
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "forge": {
/// "description": "The forge's host, lowercased.",
/// "$ref": "#/definitions/ForgeHost"
/// },
/// "mode": {
/// "description": "`bridge` — a bridge service serving this forge proves control and then acts on the forge. `manual` — no automation; the binding admin declares the namespace and people with forge access carry out the forge-side steps.",
/// "type": "string",
/// "enum": [
/// "bridge",
/// "manual"
/// ]
/// },
/// "owner": {
/// "description": "The organisation or user on that forge, lowercased.",
/// "$ref": "#/definitions/Segment"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Payload {
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///The forge's host, lowercased.
pub forge: ForgeHost,
///`bridge` — a bridge service serving this forge proves control and then acts on the forge. `manual` — no automation; the binding admin declares the namespace and people with forge access carry out the forge-side steps.
pub mode: PayloadMode,
///The organisation or user on that forge, lowercased.
pub owner: Segment,
}
impl Payload {
pub fn builder() -> builder::Payload {
Default::default()
}
}
///`bridge` — a bridge service serving this forge proves control and then acts on the forge. `manual` — no automation; the binding admin declares the namespace and people with forge access carry out the forge-side steps.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "`bridge` — a bridge service serving this forge proves control and then acts on the forge. `manual` — no automation; the binding admin declares the namespace and people with forge access carry out the forge-side steps.",
/// "type": "string",
/// "enum": [
/// "bridge",
/// "manual"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum PayloadMode {
#[serde(rename = "bridge")]
Bridge,
#[serde(rename = "manual")]
Manual,
}
impl ::std::fmt::Display for PayloadMode {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Bridge => f.write_str("bridge"),
Self::Manual => f.write_str("manual"),
}
}
}
impl ::std::str::FromStr for PayloadMode {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"bridge" => Ok(Self::Bridge),
"manual" => Ok(Self::Manual),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for PayloadMode {
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 PayloadMode {
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 PayloadMode {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///The namespace as recorded, and where to go next if it is still pending.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "description": "The namespace as recorded, and where to go next if it is still pending.",
/// "type": "object",
/// "required": [
/// "namespace"
/// ],
/// "properties": {
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "namespace": {
/// "$ref": "#/definitions/GitNamespace"
/// },
/// "next": {
/// "description": "Present when the namespace is `pending`: where the administrator goes to prove control of the forge owner (install the community's forge app, or sign in to the forge and authorise the bridge).",
/// "type": "object",
/// "required": [
/// "url"
/// ],
/// "properties": {
/// "url": {
/// "description": "An https URL, single-use, carrying the bridge's binding nonce.",
/// "type": "string",
/// "format": "uri",
/// "maxLength": 2048,
/// "pattern": "^https://"
/// }
/// },
/// "additionalProperties": false
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "response"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Response {
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
pub namespace: GitNamespace,
#[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()
}
}
///Present when the namespace is `pending`: where the administrator goes to prove control of the forge owner (install the community's forge app, or sign in to the forge and authorise the bridge).
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Present when the namespace is `pending`: where the administrator goes to prove control of the forge owner (install the community's forge app, or sign in to the forge and authorise the bridge).",
/// "type": "object",
/// "required": [
/// "url"
/// ],
/// "properties": {
/// "url": {
/// "description": "An https URL, single-use, carrying the bridge's binding nonce.",
/// "type": "string",
/// "format": "uri",
/// "maxLength": 2048,
/// "pattern": "^https://"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct ResponseNext {
///An https URL, single-use, carrying the bridge's binding nonce.
pub url: ::std::string::String,
}
impl ResponseNext {
pub fn builder() -> builder::ResponseNext {
Default::default()
}
}
///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())
})
}
}
/// Types for composing complex structures.
pub mod builder {
#[derive(Clone, Debug)]
pub struct GitNamespace {
forge: ::std::result::Result<super::ForgeHost, ::std::string::String>,
id: ::std::result::Result<super::NamespaceId, ::std::string::String>,
kind: ::std::result::Result<
::std::option::Option<super::GitNamespaceKind>,
::std::string::String,
>,
mode: ::std::result::Result<super::GitNamespaceMode, ::std::string::String>,
owner: ::std::result::Result<super::Segment, ::std::string::String>,
state: ::std::result::Result<super::GitNamespaceState, ::std::string::String>,
}
impl ::std::default::Default for GitNamespace {
fn default() -> Self {
Self {
forge: Err("no value supplied for forge".to_string()),
id: Err("no value supplied for id".to_string()),
kind: Ok(Default::default()),
mode: Err("no value supplied for mode".to_string()),
owner: Err("no value supplied for owner".to_string()),
state: Err("no value supplied for state".to_string()),
}
}
}
impl GitNamespace {
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::NamespaceId>,
T::Error: ::std::fmt::Display,
{
self.id = value
.try_into()
.map_err(|e| format!("error converting supplied value for id: {e}"));
self
}
pub fn kind<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::GitNamespaceKind>>,
T::Error: ::std::fmt::Display,
{
self.kind = value
.try_into()
.map_err(|e| format!("error converting supplied value for kind: {e}"));
self
}
pub fn mode<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::GitNamespaceMode>,
T::Error: ::std::fmt::Display,
{
self.mode = value
.try_into()
.map_err(|e| format!("error converting supplied value for mode: {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
}
pub fn state<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::GitNamespaceState>,
T::Error: ::std::fmt::Display,
{
self.state = value
.try_into()
.map_err(|e| format!("error converting supplied value for state: {e}"));
self
}
}
impl ::std::convert::TryFrom<GitNamespace> for super::GitNamespace {
type Error = super::error::ConversionError;
fn try_from(
value: GitNamespace,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
forge: value.forge?,
id: value.id?,
kind: value.kind?,
mode: value.mode?,
owner: value.owner?,
state: value.state?,
})
}
}
impl ::std::convert::From<super::GitNamespace> for GitNamespace {
fn from(value: super::GitNamespace) -> Self {
Self {
forge: Ok(value.forge),
id: Ok(value.id),
kind: Ok(value.kind),
mode: Ok(value.mode),
owner: Ok(value.owner),
state: Ok(value.state),
}
}
}
#[derive(Clone, Debug)]
pub struct Payload {
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
forge: ::std::result::Result<super::ForgeHost, ::std::string::String>,
mode: ::std::result::Result<super::PayloadMode, ::std::string::String>,
owner: ::std::result::Result<super::Segment, ::std::string::String>,
}
impl ::std::default::Default for Payload {
fn default() -> Self {
Self {
ext: Ok(Default::default()),
forge: Err("no value supplied for forge".to_string()),
mode: Err("no value supplied for mode".to_string()),
owner: Err("no value supplied for owner".to_string()),
}
}
}
impl Payload {
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 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 mode<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::PayloadMode>,
T::Error: ::std::fmt::Display,
{
self.mode = value
.try_into()
.map_err(|e| format!("error converting supplied value for mode: {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<Payload> for super::Payload {
type Error = super::error::ConversionError;
fn try_from(value: Payload) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
ext: value.ext?,
forge: value.forge?,
mode: value.mode?,
owner: value.owner?,
})
}
}
impl ::std::convert::From<super::Payload> for Payload {
fn from(value: super::Payload) -> Self {
Self {
ext: Ok(value.ext),
forge: Ok(value.forge),
mode: Ok(value.mode),
owner: Ok(value.owner),
}
}
}
#[derive(Clone, Debug)]
pub struct Response {
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
namespace: ::std::result::Result<super::GitNamespace, ::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 {
ext: Ok(Default::default()),
namespace: Err("no value supplied for namespace".to_string()),
next: Ok(Default::default()),
}
}
}
impl Response {
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 namespace<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::GitNamespace>,
T::Error: ::std::fmt::Display,
{
self.namespace = value
.try_into()
.map_err(|e| format!("error converting supplied value for namespace: {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 {
ext: value.ext?,
namespace: value.namespace?,
next: value.next?,
})
}
}
impl ::std::convert::From<super::Response> for Response {
fn from(value: super::Response) -> Self {
Self {
ext: Ok(value.ext),
namespace: Ok(value.namespace),
next: Ok(value.next),
}
}
}
#[derive(Clone, Debug)]
pub struct ResponseNext {
url: ::std::result::Result<::std::string::String, ::std::string::String>,
}
impl ::std::default::Default for ResponseNext {
fn default() -> Self {
Self {
url: Err("no value supplied for url".to_string()),
}
}
}
impl ResponseNext {
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
}
}
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 { url: value.url? })
}
}
impl ::std::convert::From<super::ResponseNext> for ResponseNext {
fn from(value: super::ResponseNext) -> Self {
Self { url: Ok(value.url) }
}
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/git-ns/namespace/bind/0.1";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"Ext\": {\n \"additionalProperties\": true,\n \"description\": \"Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.\",\n \"minProperties\": 1,\n \"propertyNames\": {\n \"pattern\": \"^[a-z][a-z0-9-]*(\\\\.[a-z0-9-]+)+$\"\n },\n \"title\": \"Ext\",\n \"type\": \"object\"\n },\n \"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 \"GitNamespace\": {\n \"additionalProperties\": false,\n \"description\": \"The VTC's binding to one owner on one forge.\",\n \"properties\": {\n \"forge\": {\n \"$ref\": \"#/$defs/ForgeHost\"\n },\n \"id\": {\n \"$ref\": \"#/$defs/NamespaceId\"\n },\n \"kind\": {\n \"description\": \"Whether the owner is an organisation or a personal account, as the forge reports it. Present once known: a bridge-mode namespace learns it when binding completes, and a manual-mode namespace MAY never learn it.\",\n \"enum\": [\n \"organization\",\n \"user\"\n ],\n \"type\": \"string\"\n },\n \"mode\": {\n \"description\": \"`bridge` — a bridge service acts on the forge for this namespace (creates repositories, projects roles, reports drift). `manual` — no automation; people with forge access carry out the steps the VTC names, and the VTC governs the rights alone.\",\n \"enum\": [\n \"bridge\",\n \"manual\"\n ],\n \"type\": \"string\"\n },\n \"owner\": {\n \"$ref\": \"#/$defs/Segment\",\n \"description\": \"The organisation or user on the forge, lowercased.\"\n },\n \"state\": {\n \"description\": \"`pending` — binding has started and the forge-side proof has not arrived yet. `bound` — the VTC governs rights under this namespace.\",\n \"enum\": [\n \"pending\",\n \"bound\"\n ],\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"id\",\n \"forge\",\n \"owner\",\n \"mode\",\n \"state\"\n ],\n \"title\": \"GitNamespace\",\n \"type\": \"object\"\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 \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"description\": \"The namespace as recorded, and where to go next if it is still pending.\",\n \"properties\": {\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"namespace\": {\n \"$ref\": \"#/$defs/GitNamespace\"\n },\n \"next\": {\n \"additionalProperties\": false,\n \"description\": \"Present when the namespace is `pending`: where the administrator goes to prove control of the forge owner (install the community's forge app, or sign in to the forge and authorise the bridge).\",\n \"properties\": {\n \"url\": {\n \"description\": \"An https URL, single-use, carrying the bridge's binding nonce.\",\n \"format\": \"uri\",\n \"maxLength\": 2048,\n \"pattern\": \"^https://\",\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"url\"\n ],\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"namespace\"\n ],\n \"title\": \"Git Namespaces — Bind Namespace — response payload\",\n \"type\": \"object\"\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 },\n \"$id\": \"https://trusttasks.org/spec/git-ns/namespace/bind/0.1\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"description\": \"A VTC administrator binds the VTC to one owner on one forge, so that the VTC governs who may create, own, maintain and commit to repositories under it. In bridge mode the response says where to send the forge owner to prove control; the namespace stays `pending` until that proof arrives.\",\n \"properties\": {\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"forge\": {\n \"$ref\": \"#/$defs/ForgeHost\",\n \"description\": \"The forge's host, lowercased.\"\n },\n \"mode\": {\n \"description\": \"`bridge` — a bridge service serving this forge proves control and then acts on the forge. `manual` — no automation; the binding admin declares the namespace and people with forge access carry out the forge-side steps.\",\n \"enum\": [\n \"bridge\",\n \"manual\"\n ],\n \"type\": \"string\"\n },\n \"owner\": {\n \"$ref\": \"#/$defs/Segment\",\n \"description\": \"The organisation or user on that forge, lowercased.\"\n }\n },\n \"required\": [\n \"forge\",\n \"owner\",\n \"mode\"\n ],\n \"title\": \"Git Namespaces — Bind Namespace — payload\",\n \"type\": \"object\"\n}\n",
);
}
impl crate::Payload for Response {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/git-ns/namespace/bind/0.1#response";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"Ext\": {\n \"additionalProperties\": true,\n \"description\": \"Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.\",\n \"minProperties\": 1,\n \"propertyNames\": {\n \"pattern\": \"^[a-z][a-z0-9-]*(\\\\.[a-z0-9-]+)+$\"\n },\n \"title\": \"Ext\",\n \"type\": \"object\"\n },\n \"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 \"GitNamespace\": {\n \"additionalProperties\": false,\n \"description\": \"The VTC's binding to one owner on one forge.\",\n \"properties\": {\n \"forge\": {\n \"$ref\": \"#/$defs/ForgeHost\"\n },\n \"id\": {\n \"$ref\": \"#/$defs/NamespaceId\"\n },\n \"kind\": {\n \"description\": \"Whether the owner is an organisation or a personal account, as the forge reports it. Present once known: a bridge-mode namespace learns it when binding completes, and a manual-mode namespace MAY never learn it.\",\n \"enum\": [\n \"organization\",\n \"user\"\n ],\n \"type\": \"string\"\n },\n \"mode\": {\n \"description\": \"`bridge` — a bridge service acts on the forge for this namespace (creates repositories, projects roles, reports drift). `manual` — no automation; people with forge access carry out the steps the VTC names, and the VTC governs the rights alone.\",\n \"enum\": [\n \"bridge\",\n \"manual\"\n ],\n \"type\": \"string\"\n },\n \"owner\": {\n \"$ref\": \"#/$defs/Segment\",\n \"description\": \"The organisation or user on the forge, lowercased.\"\n },\n \"state\": {\n \"description\": \"`pending` — binding has started and the forge-side proof has not arrived yet. `bound` — the VTC governs rights under this namespace.\",\n \"enum\": [\n \"pending\",\n \"bound\"\n ],\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"id\",\n \"forge\",\n \"owner\",\n \"mode\",\n \"state\"\n ],\n \"title\": \"GitNamespace\",\n \"type\": \"object\"\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 \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"description\": \"The namespace as recorded, and where to go next if it is still pending.\",\n \"properties\": {\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"namespace\": {\n \"$ref\": \"#/$defs/GitNamespace\"\n },\n \"next\": {\n \"additionalProperties\": false,\n \"description\": \"Present when the namespace is `pending`: where the administrator goes to prove control of the forge owner (install the community's forge app, or sign in to the forge and authorise the bridge).\",\n \"properties\": {\n \"url\": {\n \"description\": \"An https URL, single-use, carrying the bridge's binding nonce.\",\n \"format\": \"uri\",\n \"maxLength\": 2048,\n \"pattern\": \"^https://\",\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"url\"\n ],\n \"type\": \"object\"\n }\n },\n \"required\": [\n \"namespace\"\n ],\n \"title\": \"Git Namespaces — Bind Namespace — response payload\",\n \"type\": \"object\"\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 },\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::POLICY_DENIED,
error_codes::ALREADY_BOUND,
error_codes::NO_BRIDGE,
];
/// 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:policyDenied`
///
/// The community's git-namespace policy refused the request after the fixed rules passed.
///
/// Declared `retryable: false`.
pub const POLICY_DENIED: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "git-ns:policyDenied",
retryable: false,
};
/// `git-ns/namespace/bind:alreadyBound`
///
/// This VTC has already bound, or is already binding, this forge and owner.
///
/// Declared `retryable: false`.
pub const ALREADY_BOUND: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "git-ns/namespace/bind:alreadyBound",
retryable: false,
};
/// `git-ns/namespace/bind:noBridge`
///
/// `mode` is `bridge` and no bridge serving this forge is configured for this VTC.
///
/// Declared `retryable: false`.
pub const NO_BRIDGE: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "git-ns/namespace/bind:noBridge",
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:4fa07603-6606-4b9a-a5c9-824c073d1701\",\n \"type\": \"https://trusttasks.org/spec/git-ns/namespace/bind/0.1\",\n \"threadId\": \"urn:uuid:4fa07603-6606-4b9a-a5c9-824c073d1701\",\n \"issuer\": \"did:webvh:QmAliceScid1:acme-vtc.example:alice\",\n \"recipient\": \"did:webvh:QmVtcScid7:acme-vtc.example\",\n \"issuedAt\": \"2026-09-23T10:00:00Z\",\n \"payload\": {\n \"forge\": \"github.com\",\n \"owner\": \"acme\",\n \"mode\": \"bridge\"\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"did:webvh:QmAliceScid1:acme-vtc.example:alice#key-1\",\n \"created\": \"2026-09-23T10:00:00Z\",\n \"proofPurpose\": \"authentication\",\n \"proofValue\": \"zLDdgsoRSHNLhQUJF9qMYB3YE7p41YyJRe4rB9CbQ8g8jivGKgNnHxAeKMPXtCFfwoPkGBzLjq6Wmbx8C597fCB\"\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:4fa07603-6606-4b9a-a5c9-824c073d1703\",\n \"type\": \"https://trusttasks.org/spec/git-ns/namespace/bind/0.1\",\n \"threadId\": \"urn:uuid:4fa07603-6606-4b9a-a5c9-824c073d1703\",\n \"issuer\": \"did:webvh:QmAliceScid1:acme-vtc.example:alice\",\n \"recipient\": \"did:webvh:QmVtcScid7:acme-vtc.example\",\n \"issuedAt\": \"2026-09-23T10:00:00Z\",\n \"payload\": {\n \"forge\": \"codeberg.org\",\n \"owner\": \"acme\",\n \"mode\": \"manual\"\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"did:webvh:QmAliceScid1:acme-vtc.example:alice#key-1\",\n \"created\": \"2026-09-23T10:00:00Z\",\n \"proofPurpose\": \"authentication\",\n \"proofValue\": \"z5ey7bYryGdKep1V7KEgjY8qja6ZgMSoKkyMr467YWQv8F6iRCmFvJxY89aXftvDR4tuXKn7pbiFe3QsuP7y9S8\"\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:4fa07603-6606-4b9a-a5c9-824c073d1702\",\n \"type\": \"https://trusttasks.org/spec/git-ns/namespace/bind/0.1#response\",\n \"threadId\": \"urn:uuid:4fa07603-6606-4b9a-a5c9-824c073d1701\",\n \"issuer\": \"did:webvh:QmVtcScid7:acme-vtc.example\",\n \"recipient\": \"did:webvh:QmAliceScid1:acme-vtc.example:alice\",\n \"issuedAt\": \"2026-09-23T10:00:01Z\",\n \"payload\": {\n \"namespace\": {\n \"id\": \"ns_01J8Z6Q4M2\",\n \"forge\": \"github.com\",\n \"owner\": \"acme\",\n \"mode\": \"bridge\",\n \"state\": \"pending\"\n },\n \"next\": {\n \"url\": \"https://github.com/apps/acme-vgi/installations/new?state=b7Hq2xP9LmR4\"\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:01Z\",\n \"proofPurpose\": \"authentication\",\n \"proofValue\": \"zr9epKdKkNUNA8UWbWNZV8SH7ee2n9h87SkQbbc6CA99tfThEUj8Nc1PyV5oBwBBLk1CFoLc523MBEtWvwX1wXH\"\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:4fa07603-6606-4b9a-a5c9-824c073d1704\",\n \"type\": \"https://trusttasks.org/spec/git-ns/namespace/bind/0.1#response\",\n \"threadId\": \"urn:uuid:4fa07603-6606-4b9a-a5c9-824c073d1703\",\n \"issuer\": \"did:webvh:QmVtcScid7:acme-vtc.example\",\n \"recipient\": \"did:webvh:QmAliceScid1:acme-vtc.example:alice\",\n \"issuedAt\": \"2026-09-23T10:00:01Z\",\n \"payload\": {\n \"namespace\": {\n \"id\": \"ns_01J8Z7C1TX\",\n \"forge\": \"codeberg.org\",\n \"owner\": \"acme\",\n \"mode\": \"manual\",\n \"state\": \"bound\"\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:01Z\",\n \"proofPurpose\": \"authentication\",\n \"proofValue\": \"zE9R8jFQGXqHf2rD4b2Y5wjvcPsSoKndPn6yA1D99qYdAQvgrKS7ZDHUVetfRhxWy5DjfsDybJBLMBuWn5yGcDk\"\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)] = &[
(
"`forge` is a bare lowercase host: no scheme, no path.",
"{\n \"forge\": \"https://github.com\",\n \"mode\": \"bridge\",\n \"owner\": \"acme\"\n}",
),
(
"`owner` is lowercase.",
"{\n \"forge\": \"github.com\",\n \"mode\": \"bridge\",\n \"owner\": \"Acme\"\n}",
),
(
"`owner` is one segment; a repository is not a namespace.",
"{\n \"forge\": \"github.com\",\n \"mode\": \"bridge\",\n \"owner\": \"acme/widgets\"\n}",
),
(
"`mode` is `bridge` or `manual`.",
"{\n \"forge\": \"github.com\",\n \"mode\": \"app\",\n \"owner\": \"acme\"\n}",
),
(
"`mode` is required: the two modes differ in whether any forge-side proof exists.",
"{\n \"forge\": \"github.com\",\n \"owner\": \"acme\"\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
);
}
}
}