cli: `rad config schema` emits JSON Schema
Leverage `schemars` to generate a JSON Schema from our structs for configurations and those occurring within them. The output of `rad config schema` can be used by editors to provide a smoother editing experience for the configuration file. Discovery attributes (both keys and values) is greatly improved with the schema helping in the background. Refer to: - https://json-schema.org - https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings - https://schemastore.org
This commit is contained in:
parent
5a2f26eaa7
commit
b608a78806
File diff suppressed because it is too large
Load Diff
|
|
@ -46,11 +46,12 @@ tree-sitter-bash = { version = "0.23.3" }
|
||||||
tree-sitter-go = { version = "0.23.4" }
|
tree-sitter-go = { version = "0.23.4" }
|
||||||
tree-sitter-md = { version = "0.3.2" }
|
tree-sitter-md = { version = "0.3.2" }
|
||||||
zeroize = { version = "1.1" }
|
zeroize = { version = "1.1" }
|
||||||
|
schemars = { version = "1.0.0-alpha.17" }
|
||||||
|
|
||||||
[dependencies.radicle]
|
[dependencies.radicle]
|
||||||
version = "0"
|
version = "0"
|
||||||
path = "../radicle"
|
path = "../radicle"
|
||||||
features = ["logger"]
|
features = ["logger", "schemars"]
|
||||||
|
|
||||||
[dependencies.radicle-cli-test]
|
[dependencies.radicle-cli-test]
|
||||||
version = "0"
|
version = "0"
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ Usage
|
||||||
rad config init --alias <alias> [<option>...]
|
rad config init --alias <alias> [<option>...]
|
||||||
rad config edit [<option>...]
|
rad config edit [<option>...]
|
||||||
rad config get <key> [<option>...]
|
rad config get <key> [<option>...]
|
||||||
|
rad config schema [<option>...]
|
||||||
rad config set <key> <value> [<option>...]
|
rad config set <key> <value> [<option>...]
|
||||||
rad config unset <key> [<option>...]
|
rad config unset <key> [<option>...]
|
||||||
rad config push <key> <value> [<option>...]
|
rad config push <key> <value> [<option>...]
|
||||||
|
|
@ -43,6 +44,7 @@ enum Operation {
|
||||||
#[default]
|
#[default]
|
||||||
Show,
|
Show,
|
||||||
Get(String),
|
Get(String),
|
||||||
|
Schema,
|
||||||
Set(String, String),
|
Set(String, String),
|
||||||
Push(String, String),
|
Push(String, String),
|
||||||
Remove(String, String),
|
Remove(String, String),
|
||||||
|
|
@ -79,6 +81,7 @@ impl Args for Options {
|
||||||
}
|
}
|
||||||
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
|
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
|
||||||
"show" => op = Some(Operation::Show),
|
"show" => op = Some(Operation::Show),
|
||||||
|
"schema" => op = Some(Operation::Schema),
|
||||||
"edit" => op = Some(Operation::Edit),
|
"edit" => op = Some(Operation::Edit),
|
||||||
"init" => op = Some(Operation::Init),
|
"init" => op = Some(Operation::Init),
|
||||||
"get" => {
|
"get" => {
|
||||||
|
|
@ -140,6 +143,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
let profile = ctx.profile()?;
|
let profile = ctx.profile()?;
|
||||||
term::json::to_pretty(&profile.config, path.as_path())?.print();
|
term::json::to_pretty(&profile.config, path.as_path())?.print();
|
||||||
}
|
}
|
||||||
|
Operation::Schema => {
|
||||||
|
term::json::to_pretty(&schemars::schema_for!(Config), path.as_path())?.print();
|
||||||
|
}
|
||||||
Operation::Get(key) => {
|
Operation::Get(key) => {
|
||||||
let mut temp_config = RawConfig::from_file(&path)?;
|
let mut temp_config = RawConfig::from_file(&path)?;
|
||||||
let key: ConfigPath = key.into();
|
let key: ConfigPath = key.into();
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ sqlite = { version = "0.32.0", features = ["bundled"] }
|
||||||
tempfile = { version = "3.3.0" }
|
tempfile = { version = "3.3.0" }
|
||||||
thiserror = { version = "1" }
|
thiserror = { version = "1" }
|
||||||
unicode-normalization = { version = "0.1" }
|
unicode-normalization = { version = "0.1" }
|
||||||
|
schemars = { version = "1.0.0-alpha.17", optional = true }
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
@ -75,6 +76,7 @@ emojis = { version = "0.6" }
|
||||||
pretty_assertions = { version = "1.3.0" }
|
pretty_assertions = { version = "1.3.0" }
|
||||||
qcheck-macros = { version = "1", default-features = false }
|
qcheck-macros = { version = "1", default-features = false }
|
||||||
qcheck = { version = "1", default-features = false }
|
qcheck = { version = "1", default-features = false }
|
||||||
|
jsonschema = { version = "0.30" }
|
||||||
|
|
||||||
[dev-dependencies.radicle-crypto]
|
[dev-dependencies.radicle-crypto]
|
||||||
path = "../radicle-crypto"
|
path = "../radicle-crypto"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
/// CLI configuration.
|
/// CLI configuration.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "schemars",
|
||||||
|
derive(schemars::JsonSchema),
|
||||||
|
schemars(rename = "CliConfig")
|
||||||
|
)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Whether to show hints or not in the CLI.
|
/// Whether to show hints or not in the CLI.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
|
||||||
|
|
@ -81,8 +81,9 @@ impl std::fmt::Display for ExplorerUrl {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A public explorer, eg. `https://app.radicle.xyz`.
|
/// A public explorer, eg. `https://app.radicle.xyz`.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct Explorer(String);
|
pub struct Explorer(String);
|
||||||
|
|
||||||
impl Default for Explorer {
|
impl Default for Explorer {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,17 @@ pub enum IdError {
|
||||||
|
|
||||||
/// A repository identifier.
|
/// A repository identifier.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct RepoId(git::Oid);
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
pub struct RepoId(
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(
|
||||||
|
with = "String",
|
||||||
|
description = "A repository identifier. Starts with \"rad:\", followed by a multibase Base58 encoded Git object identifier.",
|
||||||
|
regex(pattern = r"rad:z[1-9a-km-zA-HJ-NP-Z]+"),
|
||||||
|
length(min = 5),
|
||||||
|
example = &"rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
|
||||||
|
))]
|
||||||
|
git::Oid,
|
||||||
|
);
|
||||||
|
|
||||||
impl fmt::Display for RepoId {
|
impl fmt::Display for RepoId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ pub mod logger;
|
||||||
pub mod node;
|
pub mod node;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod rad;
|
pub mod rad;
|
||||||
|
#[cfg(feature = "schemars")]
|
||||||
|
pub(crate) mod schemars_ext;
|
||||||
pub mod serde_ext;
|
pub mod serde_ext;
|
||||||
pub mod sql;
|
pub mod sql;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ impl Penalty {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Repository sync status for our own refs.
|
/// Repository sync status for our own refs.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||||
#[serde(tag = "status")]
|
#[serde(tag = "status")]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum SyncStatus {
|
pub enum SyncStatus {
|
||||||
|
|
@ -221,7 +221,7 @@ impl PartialOrd for SyncStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Node user agent.
|
/// Node user agent.
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
|
||||||
pub struct UserAgent(String);
|
pub struct UserAgent(String);
|
||||||
|
|
||||||
impl UserAgent {
|
impl UserAgent {
|
||||||
|
|
@ -291,10 +291,25 @@ impl AsRef<str> for UserAgent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Node alias.
|
/// Node alias, i.e. a short and memorable name for it.
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
|
||||||
#[serde(try_from = "String", into = "String")]
|
#[serde(try_from = "String", into = "String")]
|
||||||
pub struct Alias(String);
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
pub struct Alias(
|
||||||
|
// To exclude control characters, one might be inclined to use the character
|
||||||
|
// class `[[:cntrl:]]` which is understood by the `regex` crate.
|
||||||
|
// However, the patterns in JSON schema must conform to ECMA-262, which does
|
||||||
|
// not specify the character class.
|
||||||
|
// Thus, we unfold its definition from <https://www.unicode.org/reports/tr18/#cntrl>,
|
||||||
|
// which refers to the "general category" named "Cc",
|
||||||
|
// see <https://unicode.org/reports/tr44/#General_Category_Values>.
|
||||||
|
// We obtain the two ranges below from <https://www.unicode.org/notes/tn36/Categories.txt>.
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "schemars",
|
||||||
|
schemars(regex(pattern = r"^[^\x00-\x1F\x7F-\x9F\s]{0,32}$"), length(max = 32))
|
||||||
|
)]
|
||||||
|
String,
|
||||||
|
);
|
||||||
|
|
||||||
impl Alias {
|
impl Alias {
|
||||||
/// Create a new alias from a string. Panics if the string is not a valid alias.
|
/// Create a new alias from a string. Panics if the string is not a valid alias.
|
||||||
|
|
@ -411,7 +426,7 @@ impl TryFrom<&sqlite::Value> for Alias {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Options passed to the "connect" node command.
|
/// Options passed to the "connect" node command.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ConnectOptions {
|
pub struct ConnectOptions {
|
||||||
/// Establish a persistent connection.
|
/// Establish a persistent connection.
|
||||||
pub persistent: bool,
|
pub persistent: bool,
|
||||||
|
|
@ -500,10 +515,30 @@ impl<T: Serialize> CommandResult<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peer public protocol address.
|
/// Peer public protocol address.
|
||||||
#[derive(Wrapper, WrapperMut, Clone, Eq, PartialEq, Debug, Hash, From, Serialize, Deserialize)]
|
#[derive(Clone, Eq, PartialEq, Debug, Hash, From, Wrapper, WrapperMut, Serialize, Deserialize)]
|
||||||
#[wrapper(Deref, Display, FromStr)]
|
#[wrapper(Deref, Display, FromStr)]
|
||||||
#[wrapper_mut(DerefMut)]
|
#[wrapper_mut(DerefMut)]
|
||||||
pub struct Address(#[serde(with = "crate::serde_ext::string")] NetAddr<HostName>);
|
#[cfg_attr(
|
||||||
|
feature = "schemars",
|
||||||
|
derive(schemars::JsonSchema),
|
||||||
|
schemars(description = "\
|
||||||
|
An IP address, or a DNS name, or a Tor onion name, followed by the symbol ':', \
|
||||||
|
followed by a TCP port number.\
|
||||||
|
")
|
||||||
|
)]
|
||||||
|
pub struct Address(
|
||||||
|
#[serde(with = "crate::serde_ext::string")]
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(
|
||||||
|
with = "String",
|
||||||
|
regex(pattern = r"^.+:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$"),
|
||||||
|
extend("examples" = [
|
||||||
|
"xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion:8776",
|
||||||
|
"seed.example.com:8776",
|
||||||
|
"192.0.2.0:31337",
|
||||||
|
]),
|
||||||
|
))]
|
||||||
|
NetAddr<HostName>,
|
||||||
|
);
|
||||||
|
|
||||||
impl Address {
|
impl Address {
|
||||||
/// Check whether this address is from the local network.
|
/// Check whether this address is from the local network.
|
||||||
|
|
@ -667,7 +702,7 @@ 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, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Seed {
|
pub struct Seed {
|
||||||
/// The Node ID.
|
/// The Node ID.
|
||||||
|
|
@ -708,7 +743,7 @@ impl Seed {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
/// 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>")]
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use std::{fmt, net};
|
||||||
|
|
||||||
use cyphernet::addr::PeerAddr;
|
use cyphernet::addr::PeerAddr;
|
||||||
use localtime::LocalDuration;
|
use localtime::LocalDuration;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json as json;
|
use serde_json as json;
|
||||||
|
|
||||||
use crate::node;
|
use crate::node;
|
||||||
|
|
@ -57,8 +58,9 @@ pub mod seeds {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peer-to-peer network.
|
/// Peer-to-peer network.
|
||||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub enum Network {
|
pub enum Network {
|
||||||
#[default]
|
#[default]
|
||||||
Main,
|
Main,
|
||||||
|
|
@ -94,16 +96,25 @@ impl Network {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration parameters defining attributes of minima and maxima.
|
/// Configuration parameters defining attributes of minima and maxima.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct Limits {
|
pub struct Limits {
|
||||||
/// Number of routing table entries before we start pruning.
|
/// Number of routing table entries before we start pruning.
|
||||||
pub routing_max_size: usize,
|
pub routing_max_size: usize,
|
||||||
/// How long to keep a routing table entry before being pruned.
|
/// How long to keep a routing table entry before being pruned.
|
||||||
#[serde(with = "crate::serde_ext::localtime::duration")]
|
#[serde(with = "crate::serde_ext::localtime::duration")]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "schemars",
|
||||||
|
schemars(with = "crate::schemars_ext::localtime::LocalDuration")
|
||||||
|
)]
|
||||||
pub routing_max_age: LocalDuration,
|
pub routing_max_age: LocalDuration,
|
||||||
/// How long to keep a gossip message entry before pruning it.
|
/// How long to keep a gossip message entry before pruning it.
|
||||||
#[serde(with = "crate::serde_ext::localtime::duration")]
|
#[serde(with = "crate::serde_ext::localtime::duration")]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "schemars",
|
||||||
|
schemars(with = "crate::schemars_ext::localtime::LocalDuration")
|
||||||
|
)]
|
||||||
pub gossip_max_age: LocalDuration,
|
pub gossip_max_age: LocalDuration,
|
||||||
/// Maximum number of concurrent fetches per peer connection.
|
/// Maximum number of concurrent fetches per peer connection.
|
||||||
pub fetch_concurrency: usize,
|
pub fetch_concurrency: usize,
|
||||||
|
|
@ -138,10 +149,19 @@ impl Default for Limits {
|
||||||
/// Limiter for byte streams.
|
/// Limiter for byte streams.
|
||||||
///
|
///
|
||||||
/// Default: 500MiB
|
/// Default: 500MiB
|
||||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[serde(into = "String", try_from = "String")]
|
#[serde(into = "String", try_from = "String")]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "schemars",
|
||||||
|
derive(schemars::JsonSchema),
|
||||||
|
schemars(transparent)
|
||||||
|
)]
|
||||||
pub struct FetchPackSizeLimit {
|
pub struct FetchPackSizeLimit {
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "schemars",
|
||||||
|
schemars(with = "crate::schemars_ext::bytesize::ByteSize")
|
||||||
|
)]
|
||||||
limit: bytesize::ByteSize,
|
limit: bytesize::ByteSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,8 +233,9 @@ impl Default for FetchPackSizeLimit {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connection limits.
|
/// Connection limits.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct ConnectionLimits {
|
pub struct ConnectionLimits {
|
||||||
/// Max inbound connections.
|
/// Max inbound connections.
|
||||||
pub inbound: usize,
|
pub inbound: usize,
|
||||||
|
|
@ -232,16 +253,18 @@ impl Default for ConnectionLimits {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rate limts for a single connection.
|
/// Rate limts for a single connection.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct RateLimit {
|
pub struct RateLimit {
|
||||||
pub fill_rate: f64,
|
pub fill_rate: f64,
|
||||||
pub capacity: usize,
|
pub capacity: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rate limits for inbound and outbound connections.
|
/// Rate limits for inbound and outbound connections.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct RateLimits {
|
pub struct RateLimits {
|
||||||
pub inbound: RateLimit,
|
pub inbound: RateLimit,
|
||||||
pub outbound: RateLimit,
|
pub outbound: RateLimit,
|
||||||
|
|
@ -263,9 +286,30 @@ impl Default for RateLimits {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Full address used to connect to a remote node.
|
/// Full address used to connect to a remote node.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[cfg_attr(
|
||||||
pub struct ConnectAddress(#[serde(with = "crate::serde_ext::string")] PeerAddr<NodeId, Address>);
|
feature = "schemars",
|
||||||
|
derive(schemars::JsonSchema),
|
||||||
|
schemars(description = "\
|
||||||
|
A node address to connect to. Format: An Ed25519 public key in multibase encoding, \
|
||||||
|
followed by the symbol '@', followed by an IP address, or a DNS name, or a Tor onion \
|
||||||
|
name, followed by the symbol ':', followed by a TCP port number.\
|
||||||
|
")
|
||||||
|
)]
|
||||||
|
pub struct ConnectAddress(
|
||||||
|
#[serde(with = "crate::serde_ext::string")]
|
||||||
|
#[schemars(
|
||||||
|
with = "String",
|
||||||
|
regex(pattern = r"^.+@.+:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$"),
|
||||||
|
extend("examples" = [
|
||||||
|
"z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776",
|
||||||
|
"z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C@xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion:8776",
|
||||||
|
"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi@seed.example.com:8776",
|
||||||
|
"z6MkkfM3tPXNPrPevKr3uSiQtHPuwnNhu2yUVjgd2jXVsVz5@192.0.2.0:31337",
|
||||||
|
]),
|
||||||
|
)]
|
||||||
|
PeerAddr<NodeId, Address>,
|
||||||
|
);
|
||||||
|
|
||||||
impl From<PeerAddr<NodeId, Address>> for ConnectAddress {
|
impl From<PeerAddr<NodeId, Address>> for ConnectAddress {
|
||||||
fn from(value: PeerAddr<NodeId, Address>) -> Self {
|
fn from(value: PeerAddr<NodeId, Address>) -> Self {
|
||||||
|
|
@ -300,8 +344,9 @@ impl Deref for ConnectAddress {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peer configuration.
|
/// Peer configuration.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase", tag = "type")]
|
#[serde(rename_all = "camelCase", tag = "type")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub enum PeerConfig {
|
pub enum PeerConfig {
|
||||||
/// Static peer set. Connect to the configured peers and maintain the connections.
|
/// Static peer set. Connect to the configured peers and maintain the connections.
|
||||||
Static,
|
Static,
|
||||||
|
|
@ -316,8 +361,9 @@ impl Default for PeerConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Relay configuration.
|
/// Relay configuration.
|
||||||
#[derive(Debug, Copy, Clone, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub enum Relay {
|
pub enum Relay {
|
||||||
/// Always relay messages.
|
/// Always relay messages.
|
||||||
Always,
|
Always,
|
||||||
|
|
@ -329,8 +375,9 @@ pub enum Relay {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Proxy configuration.
|
/// Proxy configuration.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase", tag = "mode")]
|
#[serde(rename_all = "camelCase", tag = "mode")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub enum AddressConfig {
|
pub enum AddressConfig {
|
||||||
/// Proxy connections to this address type.
|
/// Proxy connections to this address type.
|
||||||
Proxy {
|
Proxy {
|
||||||
|
|
@ -343,8 +390,9 @@ pub enum AddressConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default seeding policy. Applies when no repository policies for the given repo are found.
|
/// Default seeding policy. Applies when no repository policies for the given repo are found.
|
||||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase", tag = "default")]
|
#[serde(rename_all = "camelCase", tag = "default")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub enum DefaultSeedingPolicy {
|
pub enum DefaultSeedingPolicy {
|
||||||
/// Allow seeding.
|
/// Allow seeding.
|
||||||
Allow {
|
Allow {
|
||||||
|
|
@ -379,13 +427,19 @@ impl From<DefaultSeedingPolicy> for SeedingPolicy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Service configuration.
|
/// Service configuration.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "schemars",
|
||||||
|
derive(schemars::JsonSchema),
|
||||||
|
schemars(rename = "NodeConfig")
|
||||||
|
)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Node alias.
|
/// Node alias.
|
||||||
pub alias: Alias,
|
pub alias: Alias,
|
||||||
/// Address to listen on.
|
/// Socket address (a combination of IPv4 or IPv6 address and TCP port) to listen on.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(example = &"127.0.0.1:8776"))]
|
||||||
pub listen: Vec<net::SocketAddr>,
|
pub listen: Vec<net::SocketAddr>,
|
||||||
/// Peer configuration.
|
/// Peer configuration.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -409,6 +463,10 @@ pub struct Config {
|
||||||
/// Log level.
|
/// Log level.
|
||||||
#[serde(default = "defaults::log")]
|
#[serde(default = "defaults::log")]
|
||||||
#[serde(with = "crate::serde_ext::string")]
|
#[serde(with = "crate::serde_ext::string")]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "schemars",
|
||||||
|
schemars(with = "crate::schemars_ext::log::Level")
|
||||||
|
)]
|
||||||
pub log: log::Level,
|
pub log: log::Level,
|
||||||
/// Whether or not our node should relay messages.
|
/// Whether or not our node should relay messages.
|
||||||
#[serde(default, deserialize_with = "crate::serde_ext::ok_or_default")]
|
#[serde(default, deserialize_with = "crate::serde_ext::ok_or_default")]
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ pub struct FollowPolicy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Seeding policy of a node or repo.
|
/// Seeding policy of a node or repo.
|
||||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase", tag = "policy")]
|
#[serde(rename_all = "camelCase", tag = "policy")]
|
||||||
pub enum SeedingPolicy {
|
pub enum SeedingPolicy {
|
||||||
/// Allow seeding.
|
/// Allow seeding.
|
||||||
|
|
@ -159,6 +159,7 @@ impl TryFrom<&sqlite::Value> for Policy {
|
||||||
/// Follow scope of a seeded repository.
|
/// Follow scope of a seeded repository.
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub enum Scope {
|
pub enum Scope {
|
||||||
/// Seed remotes that are explicitly followed.
|
/// Seed remotes that are explicitly followed.
|
||||||
Followed,
|
Followed,
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ pub enum ConfigError {
|
||||||
/// Local radicle configuration.
|
/// Local radicle configuration.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Public explorer. This is used for generating links.
|
/// Public explorer. This is used for generating links.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -360,3 +361,19 @@ impl From<String> for ConfigPath {
|
||||||
ConfigPath(parts)
|
ConfigPath(parts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn schema() {
|
||||||
|
use super::Config;
|
||||||
|
use crate::prelude::Alias;
|
||||||
|
use serde_json::to_value;
|
||||||
|
|
||||||
|
let schema = to_value(schemars::schema_for!(Config)).unwrap();
|
||||||
|
let config = to_value(Config::new(Alias::new("schema"))).unwrap();
|
||||||
|
jsonschema::validate(&schema, &config)
|
||||||
|
.expect("generated configuration should validate under generated JSON Schema");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
//! This module contains auxiliary definitions for generating JSONSchemas.
|
||||||
|
//! See <https://graham.cool/schemars/examples/5-remote_derive/>.
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
|
||||||
|
pub(crate) mod log {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// See [`::log::Level`]
|
||||||
|
#[derive(JsonSchema)]
|
||||||
|
#[schemars(
|
||||||
|
remote = "log::Level",
|
||||||
|
description = "A log level.",
|
||||||
|
rename_all = "UPPERCASE"
|
||||||
|
)]
|
||||||
|
pub(crate) enum Level {
|
||||||
|
/// Designates very serious errors.
|
||||||
|
Error,
|
||||||
|
/// Designates hazardous situations.
|
||||||
|
Warn,
|
||||||
|
/// Designates useful information.
|
||||||
|
Info,
|
||||||
|
/// Designates lower priority information.
|
||||||
|
Debug,
|
||||||
|
/// Designates very low priority, often extremely verbose, information.
|
||||||
|
Trace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod bytesize {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// See [`::bytesize::ByteSize`] as well as [`::bytesize::parse`].
|
||||||
|
/// Note that the pattern here is a little more restrictive than
|
||||||
|
/// the actual parsing logic, as it enforces particular casing and whitespace.
|
||||||
|
/// However, the regular expression is easier to read.
|
||||||
|
#[derive(JsonSchema)]
|
||||||
|
#[schemars(
|
||||||
|
remote = "bytesize::ByteSize",
|
||||||
|
description = "Byte quantities using unit prefixes according to SI or ISO/IEC 80000-13.",
|
||||||
|
extend("examples" = ["7 G", "50.3 TiB", "200 B", "4 Ki", "10 MB"]),
|
||||||
|
)]
|
||||||
|
pub(crate) struct ByteSize(
|
||||||
|
#[schemars(regex(pattern = r"^\d+(\.\d+)? ((K|M|G|T|P)i?B?|B)$"))] String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod localtime {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// See [`::localtime::LocalDuration`]
|
||||||
|
#[derive(JsonSchema)]
|
||||||
|
#[schemars(
|
||||||
|
remote = "localtime::LocalDuration",
|
||||||
|
description = "A time duration measured locally in milliseconds."
|
||||||
|
)]
|
||||||
|
pub(crate) struct LocalDuration(u64);
|
||||||
|
}
|
||||||
|
|
@ -1,29 +1,36 @@
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::prelude::RepoId;
|
use crate::prelude::RepoId;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
/// Web configuration.
|
/// Web configuration.
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "schemars",
|
||||||
|
derive(schemars::JsonSchema),
|
||||||
|
schemars(rename = "WebConfig")
|
||||||
|
)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Pinned content.
|
/// Pinned content.
|
||||||
pub pinned: Pinned,
|
pub pinned: Pinned,
|
||||||
/// URL pointing to an image used in the header of a node page.
|
/// URL pointing to an image used in the header of a node page.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(url))]
|
||||||
pub banner_url: Option<String>,
|
pub banner_url: Option<String>,
|
||||||
/// URL pointing to an image used as the node avatar.
|
/// URL pointing to an image used as the node avatar.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(url))]
|
||||||
pub avatar_url: Option<String>,
|
pub avatar_url: Option<String>,
|
||||||
/// Node description.
|
/// Node description.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
#[cfg_attr(feature = "schemars", schemars(url))]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pinned content. This can be used to pin certain content when
|
/// Pinned content. This can be used to pin certain content when
|
||||||
/// listing, e.g. pin repositories on a web client.
|
/// listing, e.g. pin repositories on a web client.
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Pinned {
|
pub struct Pinned {
|
||||||
/// Pinned repositories.
|
/// Pinned repositories.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue