radicle/schemars: Annotate Commands and results

JSON Schema extraction via `schemars` is provided for configurations.
There are other interfaces that use JSON for (de-)serialization, such as
the communication with `radicle-node` via the control socket.

To ease implementation of tools that want to communicate via the control
socket, we add the respective `schemars` annotations.
This commit is contained in:
Lorenz Leutgeb 2025-05-25 19:28:26 +02:00 committed by Fintan Halpenny
parent 05368e84fa
commit fcd1acd1dd
7 changed files with 185 additions and 10 deletions

View File

@ -24,7 +24,7 @@ pub mod node;
pub mod profile; pub mod profile;
pub mod rad; pub mod rad;
#[cfg(feature = "schemars")] #[cfg(feature = "schemars")]
pub(crate) mod schemars_ext; pub mod schemars_ext;
pub mod serde_ext; pub mod serde_ext;
pub mod sql; pub mod sql;
pub mod storage; pub mod storage;

View File

@ -96,6 +96,7 @@ pub enum PingState {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum State { pub enum State {
/// Initial state for outgoing connections. /// Initial state for outgoing connections.
Initial, Initial,
@ -106,6 +107,10 @@ pub enum State {
Connected { Connected {
/// Connected since this time. /// Connected since this time.
#[serde(with = "crate::serde_ext::localtime::time")] #[serde(with = "crate::serde_ext::localtime::time")]
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::localtime::LocalDurationInSeconds")
)]
since: LocalTime, since: LocalTime,
/// Ping state. /// Ping state.
#[serde(skip)] #[serde(skip)]
@ -124,9 +129,17 @@ pub enum State {
Disconnected { Disconnected {
/// Since when has this peer been disconnected. /// Since when has this peer been disconnected.
#[serde(with = "crate::serde_ext::localtime::time")] #[serde(with = "crate::serde_ext::localtime::time")]
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::localtime::LocalDurationInSeconds")
)]
since: LocalTime, since: LocalTime,
/// When to retry the connection. /// When to retry the connection.
#[serde(with = "crate::serde_ext::localtime::time")] #[serde(with = "crate::serde_ext::localtime::time")]
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::localtime::LocalDurationInSeconds")
)]
retry_at: LocalTime, retry_at: LocalTime,
}, },
} }
@ -185,6 +198,7 @@ impl Penalty {
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(tag = "status")] #[serde(tag = "status")]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum SyncStatus { pub enum SyncStatus {
/// We're in sync. /// We're in sync.
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -428,6 +442,7 @@ impl TryFrom<&sqlite::Value> for Alias {
/// Options passed to the "connect" node command. /// Options passed to the "connect" node command.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ConnectOptions { pub struct ConnectOptions {
/// Establish a persistent connection. /// Establish a persistent connection.
pub persistent: bool, pub persistent: bool,
@ -480,6 +495,7 @@ impl From<Event> for CommandResult<Event> {
/// A success response. /// A success response.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Success { pub struct Success {
/// Whether something was updated. /// Whether something was updated.
#[serde(default, skip_serializing_if = "crate::serde_ext::is_default")] #[serde(default, skip_serializing_if = "crate::serde_ext::is_default")]
@ -589,6 +605,7 @@ impl From<Address> for HostName {
/// Command name. /// Command name.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "command")] #[serde(rename_all = "camelCase", tag = "command")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Command { pub enum Command {
/// Announce repository references for given repository to peers. /// Announce repository references for given repository to peers.
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -616,7 +633,13 @@ pub enum Command {
/// Disconnect from a node. /// Disconnect from a node.
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
Disconnect { nid: NodeId }, Disconnect {
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::crypto::PublicKey")
)]
nid: NodeId,
},
/// Lookup seeds for the given repository in the routing table. /// Lookup seeds for the given repository in the routing table.
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -626,12 +649,22 @@ pub enum Command {
Sessions, Sessions,
/// Get a specific peer session. /// Get a specific peer session.
Session { nid: NodeId }, Session {
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::crypto::PublicKey")
)]
nid: NodeId,
},
/// Fetch the given repository from the network. /// Fetch the given repository from the network.
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
Fetch { Fetch {
rid: RepoId, rid: RepoId,
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::crypto::PublicKey")
)]
nid: NodeId, nid: NodeId,
timeout: time::Duration, timeout: time::Duration,
}, },
@ -646,11 +679,24 @@ pub enum Command {
/// Follow the given node. /// Follow the given node.
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
Follow { nid: NodeId, alias: Option<Alias> }, Follow {
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::crypto::PublicKey")
)]
nid: NodeId,
alias: Option<Alias>,
},
/// Unfollow the given node. /// Unfollow the given node.
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
Unfollow { nid: NodeId }, Unfollow {
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::crypto::PublicKey")
)]
nid: NodeId,
},
/// Get the node's status. /// Get the node's status.
Status, Status,
@ -679,6 +725,7 @@ impl Command {
/// Connection link direction. /// Connection link direction.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Link { pub enum Link {
/// Outgoing connection. /// Outgoing connection.
Outbound, Outbound,
@ -688,7 +735,12 @@ pub enum Link {
/// An established network connection with a peer. /// An established network connection with a peer.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Session { pub struct Session {
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::crypto::PublicKey")
)]
pub nid: NodeId, pub nid: NodeId,
pub link: Link, pub link: Link,
pub addr: Address, pub addr: Address,
@ -705,8 +757,14 @@ impl Session {
/// A seed for some repository, with metadata about its status. /// A seed for some repository, with metadata about its status.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Seed { pub struct Seed {
/// The Node ID. /// The Node ID.
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::crypto::PublicKey")
)]
pub nid: NodeId, pub nid: NodeId,
/// Known addresses for this seed. /// Known addresses for this seed.
pub addrs: Vec<KnownAddress>, pub addrs: Vec<KnownAddress>,
@ -748,7 +806,11 @@ impl Seed {
/// Represents a set of seeds with associated metadata. Uses an RNG /// Represents a set of seeds with associated metadata. Uses an RNG
/// underneath, so every iteration returns a different ordering. /// underneath, so every iteration returns a different ordering.
#[serde(into = "Vec<Seed>", from = "Vec<Seed>")] #[serde(into = "Vec<Seed>", from = "Vec<Seed>")]
pub struct Seeds(address::AddressBook<NodeId, Seed>); #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Seeds(
#[cfg_attr(feature = "schemars", schemars(with = "Vec<Seed>"))]
address::AddressBook<NodeId, Seed>,
);
impl Seeds { impl Seeds {
/// Create a new seeds list from an RNG. /// Create a new seeds list from an RNG.
@ -819,9 +881,14 @@ impl From<Vec<Seed>> for Seeds {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "camelCase")] #[serde(tag = "status", rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum FetchResult { pub enum FetchResult {
Success { Success {
updated: Vec<RefUpdate>, updated: Vec<RefUpdate>,
#[cfg_attr(
feature = "schemars",
schemars(with = "HashSet<crate::schemars_ext::crypto::PublicKey>")
)]
namespaces: HashSet<NodeId>, namespaces: HashSet<NodeId>,
clone: bool, clone: bool,
}, },
@ -978,6 +1045,7 @@ impl Error {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "status")] #[serde(rename_all = "camelCase", tag = "status")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum ConnectResult { pub enum ConnectResult {
Connected, Connected,
Disconnected { reason: String }, Disconnected { reason: String },

View File

@ -143,6 +143,7 @@ pub struct Node {
/// A known address. /// A known address.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct KnownAddress { pub struct KnownAddress {
/// Network address. /// Network address.
pub addr: Address, pub addr: Address,
@ -150,9 +151,17 @@ pub struct KnownAddress {
pub source: Source, pub source: Source,
/// Last time this address was used to successfully connect to a peer. /// Last time this address was used to successfully connect to a peer.
#[serde(with = "crate::serde_ext::localtime::option::time")] #[serde(with = "crate::serde_ext::localtime::option::time")]
#[cfg_attr(
feature = "schemars",
schemars(with = "Option<crate::schemars_ext::localtime::LocalDurationInSeconds>")
)]
pub last_success: Option<LocalTime>, pub last_success: Option<LocalTime>,
/// Last time this address was tried. /// Last time this address was tried.
#[serde(with = "crate::serde_ext::localtime::option::time")] #[serde(with = "crate::serde_ext::localtime::option::time")]
#[cfg_attr(
feature = "schemars",
schemars(with = "Option<crate::schemars_ext::localtime::LocalDurationInSeconds>")
)]
pub last_attempt: Option<LocalTime>, pub last_attempt: Option<LocalTime>,
/// Whether this address has been banned. /// Whether this address has been banned.
pub banned: bool, pub banned: bool,
@ -174,6 +183,7 @@ impl KnownAddress {
/// Address source. Specifies where an address originated from. /// Address source. Specifies where an address originated from.
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Source { pub enum Source {
/// An address that was shared by another peer. /// An address that was shared by another peer.
Peer, Peer,

View File

@ -11,11 +11,17 @@ use crate::storage::{refs::RefsAt, ReadRepository, RemoteId};
/// Holds an oid and timestamp. /// Holds an oid and timestamp.
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SyncedAt { pub struct SyncedAt {
/// Head of `rad/sigrefs`. /// Head of `rad/sigrefs`.
#[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
pub oid: git_ext::Oid, pub oid: git_ext::Oid,
/// When these refs were synced. /// When these refs were synced.
#[serde(with = "crate::serde_ext::localtime::time")] #[serde(with = "crate::serde_ext::localtime::time")]
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::localtime::LocalDurationInSeconds")
)]
pub timestamp: LocalTime, pub timestamp: LocalTime,
} }

View File

@ -4,6 +4,29 @@
use schemars::JsonSchema; use schemars::JsonSchema;
pub mod crypto {
use super::*;
/// See [`crate::node::NodeId`]
/// See [`crate::storage::RemoteId`]
/// See [`::crypto::PublicKey`]
///
/// An Ed25519 public key in multibase encoding.
///
/// `MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))`
#[derive(JsonSchema)]
#[schemars(
title = "NodeId",
description = "An Ed25519 public key in multibase encoding.",
extend("examples" = [
"z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7",
"z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C",
"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"z6MkkfM3tPXNPrPevKr3uSiQtHPuwnNhu2yUVjgd2jXVsVz5",
]),
)]
pub struct PublicKey(String);
}
pub(crate) mod log { pub(crate) mod log {
use super::*; use super::*;
@ -56,4 +79,34 @@ pub(crate) mod localtime {
description = "A time duration measured locally in milliseconds." description = "A time duration measured locally in milliseconds."
)] )]
pub(crate) struct LocalDuration(u64); pub(crate) struct LocalDuration(u64);
/// See [`crate::serde_ext::localtime::time`]
#[derive(JsonSchema)]
#[schemars(
remote = "localtime::LocalDuration",
description = "A time duration measured locally in seconds."
)]
pub(crate) struct LocalDurationInSeconds(u64);
}
pub(crate) mod git {
use super::*;
/// See [`crate::git::Oid`]
/// See [`::git_ext::Oid`]
/// See [`::git2::Oid`]
///
/// A Git Object Identifier in hexadecimal encoding.
#[derive(JsonSchema)]
#[schemars(
remote = "git2::Oid",
description = "A Git Object Identifier (SHA-1 or SHA-256 hash) in hexadecimal encoding."
)]
pub(crate) struct Oid(
#[schemars(regex(pattern = r"^([0-9a-fA-F]{64}|[0-9a-fA-F]{40})$"))] String,
);
/// See [`crate::git::RefString`]
#[derive(JsonSchema)]
pub(crate) struct RefString(String);
} }

View File

@ -191,11 +191,43 @@ pub type RemoteId = PublicKey;
/// An update to a reference. /// An update to a reference.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum RefUpdate { pub enum RefUpdate {
Updated { name: RefString, old: Oid, new: Oid }, Updated {
Created { name: RefString, oid: Oid }, #[cfg_attr(
Deleted { name: RefString, oid: Oid }, feature = "schemars",
Skipped { name: RefString, oid: Oid }, schemars(with = "crate::schemars_ext::git::RefString")
)]
name: RefString,
#[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
old: Oid,
#[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
new: Oid,
},
Created {
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::git::RefString")
)]
name: RefString,
#[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
oid: Oid,
},
Deleted {
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::git::RefString")
)]
name: RefString,
#[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
oid: Oid,
},
Skipped {
#[cfg_attr(feature = "schemars", schemars(with = "String"))]
name: RefString,
#[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
oid: Oid,
},
} }
impl RefUpdate { impl RefUpdate {

View File

@ -377,10 +377,16 @@ impl<V> Deref for SignedRefs<V> {
/// references to other nodes. /// references to other nodes.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct RefsAt { pub struct RefsAt {
/// The remote namespace of the `rad/sigrefs`. /// The remote namespace of the `rad/sigrefs`.
#[cfg_attr(
feature = "schemars",
schemars(with = "crate::schemars_ext::crypto::PublicKey")
)]
pub remote: RemoteId, pub remote: RemoteId,
/// The commit SHA that `rad/sigrefs` points to. /// The commit SHA that `rad/sigrefs` points to.
#[cfg_attr(feature = "schemars", schemars(with = "crate::schemars_ext::git::Oid"))]
pub at: Oid, pub at: Oid,
} }