use std::borrow::{Borrow, Cow}; use std::fmt; use std::fmt::Debug; use std::ops::RangeBounds; use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use jsonpath_lib::Compiled as CompiledJsonPath; use patch_db::{DbHandle, OptionModel}; use rand::{CryptoRng, Rng}; use regex::Regex; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{Number, Value}; use super::util::{self, CharSet, NumRange, UniqueBy, STATIC_NULL}; use super::{Config, MatchError, NoMatchWithPath, TimeoutError, TypeOf}; use crate::config::ConfigurationError; use crate::net::interface::InterfaceId; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::Error; // Config Value Specifications #[async_trait] pub trait ValueSpec { // This function defines whether the value supplied in the argument is // consistent with the spec in &self fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath>; // This function checks whether the value spec is consistent with itself, // since not all inVariant can be checked by the type fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath>; // update is to fill in values for environment pointers recursively async fn update( &self, db: &mut Db, config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError>; // returns all pointers that are live in the provided config fn pointers(&self, value: &Value) -> Result, NoMatchWithPath>; // requires returns whether the app id is the target of a pointer within it fn requires(&self, id: &PackageId, value: &Value) -> bool; // defines if 2 values of this type are equal for the purpose of uniqueness fn eq(&self, lhs: &Value, rhs: &Value) -> bool; } // Config Value Default Generation // // This behavior is defined by two independent traits as well as a third that // represents a conjunction of those two traits: // // DefaultableWith - defines an associated type describing the information it // needs to be able to generate a default value, as well as a function for // extracting relevant pieces of that information and using it to actually // generate the default value // // HasDefaultSpec - only purpose is to summon the default spec for the type // // Defaultable - this is a redundant trait that may replace 'DefaultableWith' // and 'HasDefaultSpec'. pub trait DefaultableWith { type DefaultSpec: Sync; type Error: std::error::Error; fn gen_with( &self, spec: &Self::DefaultSpec, rng: &mut R, timeout: &Option, ) -> Result; } pub trait HasDefaultSpec: DefaultableWith { fn default_spec(&self) -> &Self::DefaultSpec; } pub trait Defaultable { type Error; fn gen( &self, rng: &mut R, timeout: &Option, ) -> Result; } impl Defaultable for T where T: HasDefaultSpec + DefaultableWith + Sync, E: std::error::Error, { type Error = E; fn gen( &self, rng: &mut R, timeout: &Option, ) -> Result { self.gen_with(self.default_spec().borrow(), rng, timeout) } } // WithDefault - trivial wrapper that pairs a 'DefaultableWith' type with a // default spec #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WithDefault { #[serde(flatten)] pub inner: T, pub default: T::DefaultSpec, } impl DefaultableWith for WithDefault where T: DefaultableWith + Sync + Send, T::DefaultSpec: Send, { type DefaultSpec = T::DefaultSpec; type Error = T::Error; fn gen_with( &self, spec: &Self::DefaultSpec, rng: &mut R, timeout: &Option, ) -> Result { self.inner.gen_with(spec, rng, timeout) } } impl HasDefaultSpec for WithDefault where T: DefaultableWith + Sync + Send, T::DefaultSpec: Send, { fn default_spec(&self) -> &Self::DefaultSpec { &self.default } } #[async_trait] impl ValueSpec for WithDefault where T: ValueSpec + DefaultableWith + Send + Sync, Self: Send + Sync, { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { self.inner.matches(value) } fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { self.inner.validate(manifest) } async fn update( &self, db: &mut Db, config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError> { self.inner.update(db, config_overrides, value).await } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { self.inner.pointers(value) } fn requires(&self, id: &PackageId, value: &Value) -> bool { self.inner.requires(id, value) } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { self.inner.eq(lhs, rhs) } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WithNullable { #[serde(flatten)] pub inner: T, pub nullable: bool, } #[async_trait] impl ValueSpec for WithNullable where T: ValueSpec + Send + Sync, Self: Send + Sync, { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { match (self.nullable, value) { (true, &Value::Null) => Ok(()), _ => self.inner.matches(value), } } fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { self.inner.validate(manifest) } async fn update( &self, db: &mut Db, config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError> { self.inner.update(db, config_overrides, value).await } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { self.inner.pointers(value) } fn requires(&self, id: &PackageId, value: &Value) -> bool { self.inner.requires(id, value) } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { self.inner.eq(lhs, rhs) } } impl DefaultableWith for WithNullable where T: DefaultableWith + Sync + Send, { type DefaultSpec = T::DefaultSpec; type Error = T::Error; fn gen_with( &self, spec: &Self::DefaultSpec, rng: &mut R, timeout: &Option, ) -> Result { self.inner.gen_with(spec, rng, timeout) } } impl Defaultable for WithNullable where T: Defaultable + Sync + Send, { type Error = T::Error; fn gen( &self, rng: &mut R, timeout: &Option, ) -> Result { self.inner.gen(rng, timeout) } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct WithDescription { #[serde(flatten)] pub inner: T, pub description: Option, pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub change_warning: Option, } #[async_trait] impl ValueSpec for WithDescription where T: ValueSpec + Sync + Send, Self: Sync + Send, { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { self.inner.matches(value) } fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { self.inner.validate(manifest) } async fn update( &self, db: &mut Db, config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError> { self.inner.update(db, config_overrides, value).await } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { self.inner.pointers(value) } fn requires(&self, id: &PackageId, value: &Value) -> bool { self.inner.requires(id, value) } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { self.inner.eq(lhs, rhs) } } impl DefaultableWith for WithDescription where T: DefaultableWith + Sync + Send, { type DefaultSpec = T::DefaultSpec; type Error = T::Error; fn gen_with( &self, spec: &Self::DefaultSpec, rng: &mut R, timeout: &Option, ) -> Result { self.inner.gen_with(spec, rng, timeout) } } impl Defaultable for WithDescription where T: Defaultable + Sync + Send, { type Error = T::Error; fn gen( &self, rng: &mut R, timeout: &Option, ) -> Result { self.inner.gen(rng, timeout) } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[serde(tag = "type")] pub enum ValueSpecAny { Boolean(WithDescription>), Enum(WithDescription>), List(ValueSpecList), Number(WithDescription>>), Object(WithDescription>), String(WithDescription>>), Union(WithDescription>), Pointer(WithDescription), } impl ValueSpecAny { pub fn name<'a>(&'a self) -> &'a str { match self { ValueSpecAny::Boolean(b) => b.name.as_str(), ValueSpecAny::Enum(e) => e.name.as_str(), ValueSpecAny::List(l) => match l { ValueSpecList::Enum(e) => e.name.as_str(), ValueSpecList::Number(n) => n.name.as_str(), ValueSpecList::Object(o) => o.name.as_str(), ValueSpecList::String(s) => s.name.as_str(), ValueSpecList::Union(u) => u.name.as_str(), }, ValueSpecAny::Number(n) => n.name.as_str(), ValueSpecAny::Object(o) => o.name.as_str(), ValueSpecAny::Pointer(p) => p.name.as_str(), ValueSpecAny::String(s) => s.name.as_str(), ValueSpecAny::Union(u) => u.name.as_str(), } } } #[async_trait] impl ValueSpec for ValueSpecAny { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { match self { ValueSpecAny::Boolean(a) => a.matches(value), ValueSpecAny::Enum(a) => a.matches(value), ValueSpecAny::List(a) => a.matches(value), ValueSpecAny::Number(a) => a.matches(value), ValueSpecAny::Object(a) => a.matches(value), ValueSpecAny::String(a) => a.matches(value), ValueSpecAny::Union(a) => a.matches(value), ValueSpecAny::Pointer(a) => a.matches(value), } } fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { match self { ValueSpecAny::Boolean(a) => a.validate(manifest), ValueSpecAny::Enum(a) => a.validate(manifest), ValueSpecAny::List(a) => a.validate(manifest), ValueSpecAny::Number(a) => a.validate(manifest), ValueSpecAny::Object(a) => a.validate(manifest), ValueSpecAny::String(a) => a.validate(manifest), ValueSpecAny::Union(a) => a.validate(manifest), ValueSpecAny::Pointer(a) => a.validate(manifest), } } async fn update( &self, db: &mut Db, config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError> { match self { ValueSpecAny::Boolean(a) => a.update(db, config_overrides, value).await, ValueSpecAny::Enum(a) => a.update(db, config_overrides, value).await, ValueSpecAny::List(a) => a.update(db, config_overrides, value).await, ValueSpecAny::Number(a) => a.update(db, config_overrides, value).await, ValueSpecAny::Object(a) => a.update(db, config_overrides, value).await, ValueSpecAny::String(a) => a.update(db, config_overrides, value).await, ValueSpecAny::Union(a) => a.update(db, config_overrides, value).await, ValueSpecAny::Pointer(a) => a.update(db, config_overrides, value).await, } } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { match self { ValueSpecAny::Boolean(a) => a.pointers(value), ValueSpecAny::Enum(a) => a.pointers(value), ValueSpecAny::List(a) => a.pointers(value), ValueSpecAny::Number(a) => a.pointers(value), ValueSpecAny::Object(a) => a.pointers(value), ValueSpecAny::String(a) => a.pointers(value), ValueSpecAny::Union(a) => a.pointers(value), ValueSpecAny::Pointer(a) => a.pointers(value), } } fn requires(&self, id: &PackageId, value: &Value) -> bool { match self { ValueSpecAny::Boolean(a) => a.requires(id, value), ValueSpecAny::Enum(a) => a.requires(id, value), ValueSpecAny::List(a) => a.requires(id, value), ValueSpecAny::Number(a) => a.requires(id, value), ValueSpecAny::Object(a) => a.requires(id, value), ValueSpecAny::String(a) => a.requires(id, value), ValueSpecAny::Union(a) => a.requires(id, value), ValueSpecAny::Pointer(a) => a.requires(id, value), } } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { match self { ValueSpecAny::Boolean(a) => a.eq(lhs, rhs), ValueSpecAny::Enum(a) => a.eq(lhs, rhs), ValueSpecAny::List(a) => a.eq(lhs, rhs), ValueSpecAny::Number(a) => a.eq(lhs, rhs), ValueSpecAny::Object(a) => a.eq(lhs, rhs), ValueSpecAny::String(a) => a.eq(lhs, rhs), ValueSpecAny::Union(a) => a.eq(lhs, rhs), ValueSpecAny::Pointer(a) => a.eq(lhs, rhs), } } } impl Defaultable for ValueSpecAny { type Error = ConfigurationError; fn gen( &self, rng: &mut R, timeout: &Option, ) -> Result { match self { ValueSpecAny::Boolean(a) => a.gen(rng, timeout).map_err(crate::util::Never::absurd), ValueSpecAny::Enum(a) => a.gen(rng, timeout).map_err(crate::util::Never::absurd), ValueSpecAny::List(a) => a.gen(rng, timeout), ValueSpecAny::Number(a) => a.gen(rng, timeout).map_err(crate::util::Never::absurd), ValueSpecAny::Object(a) => a.gen(rng, timeout), ValueSpecAny::String(a) => a.gen(rng, timeout).map_err(ConfigurationError::from), ValueSpecAny::Union(a) => a.gen(rng, timeout), ValueSpecAny::Pointer(a) => a.gen(rng, timeout), } } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ValueSpecBoolean {} #[async_trait] impl ValueSpec for ValueSpecBoolean { fn matches(&self, val: &Value) -> Result<(), NoMatchWithPath> { match val { Value::Bool(_) => Ok(()), Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), a => Err(NoMatchWithPath::new(MatchError::InvalidType( "boolean", a.type_of(), ))), } } fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { Ok(()) } async fn update( &self, _db: &mut Db, _config_overrides: &IndexMap, _value: &mut Value, ) -> Result<(), ConfigurationError> { Ok(()) } fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { Ok(Vec::new()) } fn requires(&self, _id: &PackageId, _value: &Value) -> bool { false } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { match (lhs, rhs) { (Value::Bool(lhs), Value::Bool(rhs)) => lhs == rhs, _ => false, } } } impl DefaultableWith for ValueSpecBoolean { type DefaultSpec = bool; type Error = crate::util::Never; fn gen_with( &self, spec: &Self::DefaultSpec, _rng: &mut R, _timeout: &Option, ) -> Result { Ok(Value::Bool(*spec)) } } #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "kebab-case")] pub struct ValueSpecEnum { pub values: IndexSet, pub value_names: IndexMap, } impl<'de> serde::de::Deserialize<'de> for ValueSpecEnum { fn deserialize>(deserializer: D) -> Result { #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] pub struct _ValueSpecEnum { pub values: IndexSet, #[serde(default)] pub value_names: IndexMap, } let mut r#enum = _ValueSpecEnum::deserialize(deserializer)?; for name in &r#enum.values { if !r#enum.value_names.contains_key(name) { r#enum.value_names.insert(name.clone(), name.clone()); } } Ok(ValueSpecEnum { values: r#enum.values, value_names: r#enum.value_names, }) } } #[async_trait] impl ValueSpec for ValueSpecEnum { fn matches(&self, val: &Value) -> Result<(), NoMatchWithPath> { match val { Value::String(b) => { if self.values.contains(b) { Ok(()) } else { Err(NoMatchWithPath::new(MatchError::Enum( b.clone(), self.values.clone(), ))) } } Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), a => Err(NoMatchWithPath::new(MatchError::InvalidType( "string", a.type_of(), ))), } } fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { Ok(()) } async fn update( &self, _db: &mut Db, _config_overrides: &IndexMap, _value: &mut Value, ) -> Result<(), ConfigurationError> { Ok(()) } fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { Ok(Vec::new()) } fn requires(&self, _id: &PackageId, _value: &Value) -> bool { false } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { match (lhs, rhs) { (Value::String(lhs), Value::String(rhs)) => lhs == rhs, _ => false, } } } impl DefaultableWith for ValueSpecEnum { type DefaultSpec = String; type Error = crate::util::Never; fn gen_with( &self, spec: &Self::DefaultSpec, _rng: &mut R, _timeout: &Option, ) -> Result { Ok(Value::String(spec.clone())) } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ListSpec { pub spec: T, pub range: NumRange, } #[async_trait] impl ValueSpec for ListSpec where T: ValueSpec + Sync + Send, Self: Sync + Send, { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { match value { Value::Array(l) => { if !self.range.contains(&l.len()) { Err(NoMatchWithPath { path: Vec::new(), error: MatchError::LengthMismatch(self.range.clone(), l.len()), }) } else { l.iter() .enumerate() .map(|(i, v)| { self.spec .matches(v) .map_err(|e| e.prepend(format!("{}", i)))?; if l.iter() .enumerate() .any(|(i2, v2)| i != i2 && self.spec.eq(v, v2)) { Err(NoMatchWithPath::new(MatchError::ListUniquenessViolation) .prepend(format!("{}", i))) } else { Ok(()) } }) .collect() } } Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), a => Err(NoMatchWithPath::new(MatchError::InvalidType( "list", a.type_of(), ))), } } fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { self.spec.validate(manifest) } async fn update( &self, db: &mut Db, config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError> { if let Value::Array(ref mut ls) = value { for (i, val) in ls.into_iter().enumerate() { match self.spec.update(db, config_overrides, val).await { Err(ConfigurationError::NoMatch(e)) => { Err(ConfigurationError::NoMatch(e.prepend(format!("{}", i)))) } a => a, }?; } Ok(()) } else { Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::InvalidType("list", value.type_of()), ))) } } fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { Ok(Vec::new()) } fn requires(&self, id: &PackageId, value: &Value) -> bool { if let Value::Array(ref ls) = value { ls.into_iter().any(|v| self.spec.requires(id, v)) } else { false } } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { match (lhs, rhs) { (Value::Array(lhs), Value::Array(rhs)) => { lhs.iter().zip_longest(rhs.iter()).all(|zip| match zip { itertools::EitherOrBoth::Both(lhs, rhs) => lhs == rhs, _ => false, }) } _ => false, } } } impl DefaultableWith for ListSpec where T: DefaultableWith + Sync + Send, { type DefaultSpec = Vec; type Error = T::Error; fn gen_with( &self, spec: &Self::DefaultSpec, rng: &mut R, timeout: &Option, ) -> Result { let mut res = Vec::new(); for spec_member in spec.iter() { res.push(self.spec.gen_with(spec_member, rng, timeout)?); } Ok(Value::Array(res)) } } unsafe impl Sync for ValueSpecObject {} // TODO: remove unsafe impl Send for ValueSpecObject {} // TODO: remove unsafe impl Sync for ValueSpecUnion {} // TODO: remove unsafe impl Send for ValueSpecUnion {} // TODO: remove #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[serde(tag = "subtype")] pub enum ValueSpecList { Enum(WithDescription>>), Number(WithDescription>>), Object(WithDescription>>), String(WithDescription>>), Union(WithDescription>>>), } #[async_trait] impl ValueSpec for ValueSpecList { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { match self { ValueSpecList::Enum(a) => a.matches(value), ValueSpecList::Number(a) => a.matches(value), ValueSpecList::Object(a) => a.matches(value), ValueSpecList::String(a) => a.matches(value), ValueSpecList::Union(a) => a.matches(value), } } fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { match self { ValueSpecList::Enum(a) => a.validate(manifest), ValueSpecList::Number(a) => a.validate(manifest), ValueSpecList::Object(a) => a.validate(manifest), ValueSpecList::String(a) => a.validate(manifest), ValueSpecList::Union(a) => a.validate(manifest), } } async fn update( &self, db: &mut Db, config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError> { match self { ValueSpecList::Enum(a) => a.update(db, config_overrides, value).await, ValueSpecList::Number(a) => a.update(db, config_overrides, value).await, ValueSpecList::Object(a) => a.update(db, config_overrides, value).await, ValueSpecList::String(a) => a.update(db, config_overrides, value).await, ValueSpecList::Union(a) => a.update(db, config_overrides, value).await, } } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { match self { ValueSpecList::Enum(a) => a.pointers(value), ValueSpecList::Number(a) => a.pointers(value), ValueSpecList::Object(a) => a.pointers(value), ValueSpecList::String(a) => a.pointers(value), ValueSpecList::Union(a) => a.pointers(value), } } fn requires(&self, id: &PackageId, value: &Value) -> bool { match self { ValueSpecList::Enum(a) => a.requires(id, value), ValueSpecList::Number(a) => a.requires(id, value), ValueSpecList::Object(a) => a.requires(id, value), ValueSpecList::String(a) => a.requires(id, value), ValueSpecList::Union(a) => a.requires(id, value), } } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { match self { ValueSpecList::Enum(a) => a.eq(lhs, rhs), ValueSpecList::Number(a) => a.eq(lhs, rhs), ValueSpecList::Object(a) => a.eq(lhs, rhs), ValueSpecList::String(a) => a.eq(lhs, rhs), ValueSpecList::Union(a) => a.eq(lhs, rhs), } } } impl Defaultable for ValueSpecList { type Error = ConfigurationError; fn gen( &self, rng: &mut R, timeout: &Option, ) -> Result { match self { ValueSpecList::Enum(a) => a.gen(rng, timeout).map_err(crate::util::Never::absurd), ValueSpecList::Number(a) => a.gen(rng, timeout).map_err(crate::util::Never::absurd), ValueSpecList::Object(a) => { let mut ret = match a.gen(rng, timeout).unwrap() { Value::Array(l) => l, a => { return Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::InvalidType("list", a.type_of()), ))) } }; while !( a.inner.inner.range.start_bound(), std::ops::Bound::Unbounded, ) .contains(&ret.len()) { ret.push( a.inner .inner .spec .gen(rng, timeout) .map_err(ConfigurationError::from)?, ); } Ok(Value::Array(ret)) } ValueSpecList::String(a) => a.gen(rng, timeout).map_err(ConfigurationError::from), ValueSpecList::Union(a) => a.gen(rng, timeout).map_err(ConfigurationError::from), } } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ValueSpecNumber { range: Option>, #[serde(default)] integral: bool, #[serde(skip_serializing_if = "Option::is_none")] units: Option, } #[async_trait] impl ValueSpec for ValueSpecNumber { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { match value { Value::Number(n) => { let n = n.as_f64().unwrap(); if self.integral && n.floor() != n { return Err(NoMatchWithPath::new(MatchError::NonIntegral(n))); } if let Some(range) = &self.range { if !range.contains(&n) { return Err(NoMatchWithPath::new(MatchError::OutOfRange( range.clone(), n, ))); } } Ok(()) } Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), a => Err(NoMatchWithPath::new(MatchError::InvalidType( "object", a.type_of(), ))), } } fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { Ok(()) } async fn update( &self, _db: &mut Db, _config_overrides: &IndexMap, _value: &mut Value, ) -> Result<(), ConfigurationError> { Ok(()) } fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { Ok(Vec::new()) } fn requires(&self, _id: &PackageId, _value: &Value) -> bool { false } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { match (lhs, rhs) { (Value::Number(lhs), Value::Number(rhs)) => lhs == rhs, _ => false, } } } // TODO: remove // #[derive(Clone, Copy, Debug, Serialize)] // pub struct Number(pub f64); // impl<'de> serde::de::Deserialize<'de> for Number { // fn deserialize(deserializer: D) -> Result // where // D: serde::de::Deserializer<'de>, // { // use serde::de::*; // struct NumberVisitor; // impl<'de> Visitor<'de> for NumberVisitor { // type Value = Number; // fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { // formatter.write_str("a number") // } // fn visit_i8(self, value: i8) -> Result { // Ok(Number(value.into())) // } // fn visit_i16(self, value: i16) -> Result { // Ok(Number(value.into())) // } // fn visit_i32(self, value: i32) -> Result { // Ok(Number(value.into())) // } // fn visit_i64(self, value: i64) -> Result { // Ok(Number(value as f64)) // } // fn visit_u8(self, value: u8) -> Result { // Ok(Number(value.into())) // } // fn visit_u16(self, value: u16) -> Result { // Ok(Number(value.into())) // } // fn visit_u32(self, value: u32) -> Result { // Ok(Number(value.into())) // } // fn visit_u64(self, value: u64) -> Result { // Ok(Number(value as f64)) // } // fn visit_f32(self, value: f32) -> Result { // Ok(Number(value.into())) // } // fn visit_f64(self, value: f64) -> Result { // Ok(Number(value)) // } // } // deserializer.deserialize_any(NumberVisitor) // } // } impl DefaultableWith for ValueSpecNumber { type DefaultSpec = Option; type Error = crate::util::Never; fn gen_with( &self, spec: &Self::DefaultSpec, _rng: &mut R, _timeout: &Option, ) -> Result { Ok(spec .clone() .map(|s| Value::Number(s)) .unwrap_or(Value::Null)) } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct ValueSpecObject { pub spec: ConfigSpec, #[serde(default)] pub null_by_default: bool, pub display_as: Option, #[serde(default)] pub unique_by: UniqueBy, } #[async_trait] impl ValueSpec for ValueSpecObject { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { match value { Value::Object(o) => self.spec.matches(o), Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), a => Err(NoMatchWithPath::new(MatchError::InvalidType( "object", a.type_of(), ))), } } fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { self.spec.validate(manifest) } async fn update( &self, db: &mut Db, config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError> { if let Value::Object(o) = value { self.spec.update(db, config_overrides, o).await } else { Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::InvalidType("object", value.type_of()), ))) } } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { if let Value::Object(o) = value { self.spec.pointers(o) } else { Err(NoMatchWithPath::new(MatchError::InvalidType( "object", value.type_of(), ))) } } fn requires(&self, id: &PackageId, value: &Value) -> bool { if let Value::Object(o) = value { self.spec.requires(id, o) } else { false } } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { match (lhs, rhs) { (Value::Object(lhs), Value::Object(rhs)) => self.unique_by.eq(lhs, rhs), _ => false, } } } impl DefaultableWith for ValueSpecObject { type DefaultSpec = Config; type Error = crate::util::Never; fn gen_with( &self, spec: &Self::DefaultSpec, _rng: &mut R, _timeout: &Option, ) -> Result { if self.null_by_default { Ok(Value::Null) } else { Ok(Value::Object(spec.clone())) } } } impl Defaultable for ValueSpecObject { type Error = ConfigurationError; fn gen( &self, rng: &mut R, timeout: &Option, ) -> Result { if self.null_by_default { Ok(Value::Null) } else { self.spec.gen(rng, timeout).map(Value::Object) } } } #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct ConfigSpec(pub IndexMap); impl ConfigSpec { pub fn matches(&self, value: &Config) -> Result<(), NoMatchWithPath> { for (key, val) in self.0.iter() { if let Some(v) = value.get(key) { val.matches(v).map_err(|e| e.prepend(key.clone()))?; } else { val.matches(&Value::Null) .map_err(|e| e.prepend(key.clone()))?; } } Ok(()) } pub fn gen( &self, rng: &mut R, timeout: &Option, ) -> Result { let mut res = Config::new(); for (key, val) in self.0.iter() { res.insert(key.clone(), val.gen(rng, timeout)?); } Ok(res) } pub fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { for (name, val) in &self.0 { val.validate(manifest) .map_err(|e| e.prepend(name.clone()))?; } Ok(()) } pub async fn update( &self, db: &mut Db, config_overrides: &IndexMap, cfg: &mut Config, ) -> Result<(), ConfigurationError> { for (k, vs) in self.0.iter() { match cfg.get_mut(k) { None => { let mut v = Value::Null; vs.update(db, config_overrides, &mut v).await?; cfg.insert(k.clone(), v); } Some(v) => match vs.update(db, config_overrides, v).await { Err(ConfigurationError::NoMatch(e)) => { Err(ConfigurationError::NoMatch(e.prepend(k.clone()))) } a => a, }?, }; } Ok(()) } pub fn pointers(&self, cfg: &Config) -> Result, NoMatchWithPath> { let mut res = Vec::new(); for (k, v) in cfg.iter() { match self.0.get(k) { None => (), Some(vs) => match vs.pointers(v) { Err(e) => return Err(e.prepend(k.clone())), Ok(mut a) => res.append(&mut a), }, }; } Ok(res) } pub fn requires(&self, id: &PackageId, cfg: &Config) -> bool { self.0 .iter() .any(|(k, v)| v.requires(id, cfg.get(k).unwrap_or(&STATIC_NULL))) } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Pattern { #[serde(with = "util::serde_regex")] pub pattern: Regex, pub pattern_description: String, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ValueSpecString { #[serde(flatten)] pub pattern: Option, #[serde(default)] pub copyable: bool, #[serde(default)] pub masked: bool, } #[async_trait] impl ValueSpec for ValueSpecString { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { match value { Value::String(s) => { if let Some(pattern) = &self.pattern { if pattern.pattern.is_match(s) { Ok(()) } else { Err(NoMatchWithPath::new(MatchError::Pattern( s.to_owned(), pattern.pattern.clone(), ))) } } else { Ok(()) } } Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), a => Err(NoMatchWithPath::new(MatchError::InvalidType( "string", a.type_of(), ))), } } fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { Ok(()) } async fn update( &self, _db: &mut Db, _config_overrides: &IndexMap, _value: &mut Value, ) -> Result<(), ConfigurationError> { Ok(()) } fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { Ok(Vec::new()) } fn requires(&self, _id: &PackageId, _value: &Value) -> bool { false } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { match (lhs, rhs) { (Value::String(lhs), Value::String(rhs)) => lhs == rhs, _ => false, } } } impl DefaultableWith for ValueSpecString { type DefaultSpec = Option; type Error = TimeoutError; fn gen_with( &self, spec: &Self::DefaultSpec, rng: &mut R, timeout: &Option, ) -> Result { if let Some(spec) = spec { let now = timeout.as_ref().map(|_| std::time::Instant::now()); loop { let candidate = spec.gen(rng); match (spec, &self.pattern) { (DefaultString::Entropy(_), Some(pattern)) if !pattern.pattern.is_match(&candidate) => { () } _ => { return Ok(Value::String(candidate)); } } if let (Some(now), Some(timeout)) = (now, timeout) { if &now.elapsed() > timeout { return Err(TimeoutError); } } } } else { Ok(Value::Null) } } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum DefaultString { Literal(String), Entropy(Entropy), } impl DefaultString { pub fn gen(&self, rng: &mut R) -> String { match self { DefaultString::Literal(s) => s.clone(), DefaultString::Entropy(e) => e.gen(rng), } } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Entropy { pub charset: Option, pub len: usize, } impl Entropy { pub fn gen(&self, rng: &mut R) -> String { let len = self.len; let set = self .charset .as_ref() .map(|cs| Cow::Borrowed(cs)) .unwrap_or_else(|| Cow::Owned(Default::default())); std::iter::repeat_with(|| set.gen(rng)).take(len).collect() } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct UnionTag { pub id: String, pub name: String, pub description: Option, pub variant_names: IndexMap, } #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "kebab-case")] pub struct ValueSpecUnion { pub tag: UnionTag, pub variants: IndexMap, pub display_as: Option, pub unique_by: UniqueBy, } impl<'de> serde::de::Deserialize<'de> for ValueSpecUnion { fn deserialize>(deserializer: D) -> Result { #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] #[serde(untagged)] pub enum _UnionTag { Old(String), New(UnionTag), } #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] pub struct _ValueSpecUnion { pub variants: IndexMap, pub tag: _UnionTag, pub display_as: Option, #[serde(default)] pub unique_by: UniqueBy, } let u = _ValueSpecUnion::deserialize(deserializer)?; Ok(ValueSpecUnion { tag: match u.tag { _UnionTag::Old(id) => UnionTag { id: id.clone(), name: id, description: None, variant_names: u .variants .keys() .map(|k| (k.to_owned(), k.to_owned())) .collect(), }, _UnionTag::New(UnionTag { id, name, description, mut variant_names, }) => UnionTag { id, name, description, variant_names: { let mut iter = u.variants.keys(); while variant_names.len() < u.variants.len() { if let Some(variant) = iter.next() { variant_names.insert(variant.to_owned(), variant.to_owned()); } else { break; } } variant_names }, }, }, variants: u.variants, display_as: u.display_as, unique_by: u.unique_by, }) } } #[async_trait] impl ValueSpec for ValueSpecUnion { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { match value { Value::Object(o) => { if let Some(Value::String(ref tag)) = o.get(&self.tag.id) { if let Some(obj_spec) = self.variants.get(tag) { let mut without_tag = o.clone(); without_tag.remove(&self.tag.id); obj_spec.matches(&without_tag) } else { Err(NoMatchWithPath::new(MatchError::Union( tag.clone(), self.variants.keys().cloned().collect(), ))) } } else { Err(NoMatchWithPath::new(MatchError::MissingTag( self.tag.id.clone(), ))) } } Value::Null => Err(NoMatchWithPath::new(MatchError::NotNullable)), a => Err(NoMatchWithPath::new(MatchError::InvalidType( "object", a.type_of(), ))), } } fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { for (name, variant) in &self.variants { if variant.0.get(&self.tag.id).is_some() { return Err(NoMatchWithPath::new(MatchError::PropertyMatchesUnionTag( self.tag.id.clone(), name.clone(), ))); } variant.validate(manifest)?; } Ok(()) } async fn update( &self, db: &mut Db, config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError> { if let Value::Object(o) = value { match o.get(&self.tag.id) { None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::MissingTag(self.tag.id.clone()), ))), Some(Value::String(tag)) => match self.variants.get(tag) { None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::Union(tag.clone(), self.variants.keys().cloned().collect()), ))), Some(spec) => spec.update(db, config_overrides, o).await, }, Some(other) => Err(ConfigurationError::NoMatch( NoMatchWithPath::new(MatchError::InvalidType("string", other.type_of())) .prepend(self.tag.id.clone()), )), } } else { Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::InvalidType("object", value.type_of()), ))) } } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { if let Value::Object(o) = value { match o.get(&self.tag.id) { None => Err(NoMatchWithPath::new(MatchError::MissingTag( self.tag.id.clone(), ))), Some(Value::String(tag)) => match self.variants.get(tag) { None => Err(NoMatchWithPath::new(MatchError::Union( tag.clone(), self.variants.keys().cloned().collect(), ))), Some(spec) => spec.pointers(o), }, Some(other) => Err(NoMatchWithPath::new(MatchError::InvalidType( "string", other.type_of(), )) .prepend(self.tag.id.clone())), } } else { Err(NoMatchWithPath::new(MatchError::InvalidType( "object", value.type_of(), ))) } } fn requires(&self, id: &PackageId, value: &Value) -> bool { if let Value::Object(o) = value { match o.get(&self.tag.id) { Some(Value::String(tag)) => match self.variants.get(tag) { None => false, Some(spec) => spec.requires(id, o), }, _ => false, } } else { false } } fn eq(&self, lhs: &Value, rhs: &Value) -> bool { match (lhs, rhs) { (Value::Object(lhs), Value::Object(rhs)) => self.unique_by.eq(lhs, rhs), _ => false, } } } impl DefaultableWith for ValueSpecUnion { type DefaultSpec = String; type Error = ConfigurationError; fn gen_with( &self, spec: &Self::DefaultSpec, rng: &mut R, timeout: &Option, ) -> Result { let variant = if let Some(v) = self.variants.get(spec) { v } else { return Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::Union(spec.clone(), self.variants.keys().cloned().collect()), ))); }; let cfg_res = variant.gen(rng, timeout)?; let mut tagged_cfg = Config::new(); tagged_cfg.insert(self.tag.id.clone(), Value::String(spec.clone())); tagged_cfg.extend(cfg_res.into_iter()); Ok(Value::Object(tagged_cfg)) } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "subtype")] #[serde(rename_all = "kebab-case")] pub enum ValueSpecPointer { Package(PackagePointerSpec), System(SystemPointerSpec), } impl fmt::Display for ValueSpecPointer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ValueSpecPointer::Package(p) => write!(f, "{}", p), ValueSpecPointer::System(p) => write!(f, "{}", p), } } } impl Defaultable for ValueSpecPointer { type Error = ConfigurationError; fn gen( &self, _rng: &mut R, _timeout: &Option, ) -> Result { Ok(Value::Null) } } #[async_trait] impl ValueSpec for ValueSpecPointer { fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> { match self { ValueSpecPointer::Package(a) => a.matches(value), ValueSpecPointer::System(a) => a.matches(value), } } fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { match self { ValueSpecPointer::Package(a) => a.validate(manifest), ValueSpecPointer::System(a) => a.validate(manifest), } } async fn update( &self, db: &mut Db, config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError> { match self { ValueSpecPointer::Package(a) => a.update(db, config_overrides, value).await, ValueSpecPointer::System(a) => a.update(db, config_overrides, value).await, } } fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { Ok(vec![self.clone()]) } fn requires(&self, id: &PackageId, value: &Value) -> bool { match self { ValueSpecPointer::Package(a) => a.requires(id, value), ValueSpecPointer::System(a) => a.requires(id, value), } } fn eq(&self, _lhs: &Value, _rhs: &Value) -> bool { false } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct PackagePointerSpec { pub package_id: PackageId, #[serde(flatten)] pub target: PackagePointerSpecVariant, } impl fmt::Display for PackagePointerSpec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}: {}", self.package_id, self.target) } } impl PackagePointerSpec { async fn deref( &self, db: &mut Db, config_overrides: &IndexMap, ) -> Result { match &self.target { PackagePointerSpecVariant::TorAddress { interface } => { let addr = crate::db::DatabaseModel::new() .package_data() .idx_model(&self.package_id) .and_then(|pde| pde.installed()) .and_then(|installed| installed.interface_addresses().idx_model(interface)) .and_then(|addresses| addresses.tor_address()) .get(db, true) .await .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; Ok(addr.to_owned().map(Value::String).unwrap_or(Value::Null)) } PackagePointerSpecVariant::LanAddress { interface } => { let addr = crate::db::DatabaseModel::new() .package_data() .idx_model(&self.package_id) .and_then(|pde| pde.installed()) .and_then(|installed| installed.interface_addresses().idx_model(interface)) .and_then(|addresses| addresses.lan_address()) .get(db, true) .await .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; Ok(addr.to_owned().map(Value::String).unwrap_or(Value::Null)) } PackagePointerSpecVariant::Config { selector, multi } => { if let Some(cfg) = config_overrides.get(&self.package_id) { Ok(selector.select(*multi, &Value::Object(cfg.clone()))) } else { let manifest_model: OptionModel<_> = crate::db::DatabaseModel::new() .package_data() .idx_model(&self.package_id) .and_then(|pde| pde.installed()) .map(|installed| installed.manifest()) .into(); let version = manifest_model .clone() .map(|manifest| manifest.version()) .get(db, true) .await .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; let cfg_actions = manifest_model .clone() .and_then(|manifest| manifest.config()) .get(db, true) .await .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; let volumes = manifest_model .map(|manifest| manifest.volumes()) .get(db, true) .await .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; if let (Some(version), Some(cfg_actions), Some(volumes)) = (&*version, &*cfg_actions, &*volumes) { let cfg_res = cfg_actions .get(&self.package_id, version, volumes) .await .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; if let Some(cfg) = cfg_res.config { Ok(selector.select(*multi, &Value::Object(cfg))) } else { Ok(Value::Null) } } else { Ok(Value::Null) } } } } } } impl Defaultable for PackagePointerSpec { type Error = ConfigurationError; fn gen( &self, _rng: &mut R, _timeout: &Option, ) -> Result { Ok(Value::Null) } } #[async_trait] impl ValueSpec for PackagePointerSpec { fn matches(&self, _value: &Value) -> Result<(), NoMatchWithPath> { Ok(()) } fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> { if manifest.id != self.package_id && !manifest.dependencies.0.contains_key(&self.package_id) { return Err(NoMatchWithPath::new(MatchError::InvalidPointer( ValueSpecPointer::Package(self.clone()), ))); } match self.target { _ => Ok(()), } } async fn update( &self, db: &mut Db, config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError> { *value = self.deref(db, config_overrides).await?; Ok(()) } fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { Ok(vec![ValueSpecPointer::Package(self.clone())]) } fn requires(&self, id: &PackageId, _value: &Value) -> bool { &self.package_id == id } fn eq(&self, _lhs: &Value, _rhs: &Value) -> bool { false } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "target")] #[serde(rename_all = "kebab-case")] pub enum PackagePointerSpecVariant { TorAddress { interface: InterfaceId, }, LanAddress { interface: InterfaceId, }, Config { selector: Arc, multi: bool, }, } impl fmt::Display for PackagePointerSpecVariant { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::TorAddress { interface } => write!(f, "tor-address: {}", interface), Self::LanAddress { interface } => write!(f, "lan-address: {}", interface), Self::Config { selector, .. } => write!(f, "config: {}", selector), } } } #[derive(Clone, Debug)] pub struct ConfigSelector { src: String, compiled: CompiledJsonPath, } impl ConfigSelector { pub fn select(&self, multi: bool, val: &Value) -> Value { let selected = self.compiled.select(&val).ok().unwrap_or_else(Vec::new); if multi { Value::Array(selected.into_iter().cloned().collect()) } else { selected.get(0).map(|v| (*v).clone()).unwrap_or(Value::Null) } } } impl fmt::Display for ConfigSelector { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.src) } } impl Serialize for ConfigSelector { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&self.src) } } impl<'de> Deserialize<'de> for ConfigSelector { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let src: String = Deserialize::deserialize(deserializer)?; let compiled = CompiledJsonPath::compile(&src).map_err(serde::de::Error::custom)?; Ok(Self { src, compiled }) } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[serde(tag = "target")] pub enum SystemPointerSpec {} impl fmt::Display for SystemPointerSpec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "SYSTEM: {}", match *self {}) } } impl SystemPointerSpec { async fn deref(&self, db: &mut Db) -> Result { Ok(match *self {}) } } impl Defaultable for SystemPointerSpec { type Error = ConfigurationError; fn gen( &self, _rng: &mut R, _timeout: &Option, ) -> Result { Ok(Value::Null) } } #[async_trait] impl ValueSpec for SystemPointerSpec { fn matches(&self, _value: &Value) -> Result<(), NoMatchWithPath> { Ok(()) } fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> { Ok(()) } async fn update( &self, db: &mut Db, _config_overrides: &IndexMap, value: &mut Value, ) -> Result<(), ConfigurationError> { *value = self.deref(db).await?; Ok(()) } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { Ok(vec![ValueSpecPointer::System(self.clone())]) } fn requires(&self, _id: &PackageId, _value: &Value) -> bool { false } fn eq(&self, _lhs: &Value, _rhs: &Value) -> bool { false } } #[cfg(test)] mod test { use rand::SeedableRng; use super::*; #[test] fn test_config() { let spec = serde_json::json!({ "randomEnum": { "name": "Random Enum", "type": "enum", "default": "null", "description": "This is not even real.", "changeWarning": "Be careful chnaging this!", "values": [ "null", "option1", "option2", "option3" ] }, "testnet": { "name": "Testnet", "type": "boolean", "description": "determines whether your node is running ontestnet or mainnet", "changeWarning": "Chain will have to resync!", "default": false }, "favoriteNumber": { "name": "Favorite Number", "type": "number", "integral": false, "description": "Your favorite number of all time", "changeWarning": "Once you set this number, it can never be changed without severe consequences.", "nullable": false, "default": 7, "range": "(-100,100]" }, "secondaryNumbers": { "name": "Unlucky Numbers", "type": "list", "subtype": "number", "description": "Numbers that you like but are not your top favorite.", "spec": { "type": "number", "integral": false, "range": "[-100,200)" }, "range": "[0,10]", "default": [ 2, 3 ] }, "rpcsettings": { "name": "RPC Settings", "type": "object", "description": "rpc username and password", "changeWarning": "Adding RPC users gives them special permissions on your node.", "nullable": false, "nullByDefault": false, "spec": { "laws": { "name": "Laws", "type": "object", "description": "the law of the realm", "nullable": true, "nullByDefault": true, "spec": { "law1": { "name": "First Law", "type": "string", "description": "the first law", "nullable": true }, "law2": { "name": "Second Law", "type": "string", "description": "the second law", "nullable": true } } }, "rulemakers": { "name": "Rule Makers", "type": "list", "subtype": "object", "description": "the people who make the rules", "range": "[0,2]", "default": [], "spec": { "type": "object", "spec": { "rulemakername": { "name": "Rulemaker Name", "type": "string", "description": "the name of the rule maker", "nullable": false, "default": { "charset": "a-g,2-9", "len": 12 } }, "rulemakerip": { "name": "Rulemaker IP", "type": "string", "description": "the ip of the rule maker", "nullable": false, "default": "192.168.1.0", "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", "patternDescription": "may only contain numbers and periods" } } } }, "rpcuser": { "name": "RPC Username", "type": "string", "description": "rpc username", "nullable": false, "default": "defaultrpcusername", "pattern": "^[a-zA-Z]+$", "patternDescription": "must contain only letters." }, "rpcpass": { "name": "RPC User Password", "type": "string", "description": "rpc password", "nullable": false, "default": { "charset": "a-z,A-Z,2-9", "len": 20 } } } }, "advanced": { "name": "Advanced", "type": "object", "description": "Advanced settings", "nullable": false, "nullByDefault": false, "spec": { "notifications": { "name": "Notification Preferences", "type": "list", "subtype": "enum", "description": "how you want to be notified", "range": "[1,3]", "default": [ "email" ], "spec": { "type": "enum", "values": [ "email", "text", "call", "push", "webhook" ] } } } }, "bitcoinNode": { "name": "Bitcoin Node Settings", "type": "union", "description": "The node settings", "default": "internal", "tag": { "id": "type", "name": "Type", "variantNames": {} }, "Variant": { "internal": { "lan-address": { "name": "LAN Address", "type": "pointer", "subtype": "app", "target": "lan-address", "app-id": "bitcoind", "description": "the lan address" } }, "external": { "public-domain": { "name": "Public Domain", "type": "string", "description": "the public address of the node", "nullable": false, "default": "bitcoinnode.com", "pattern": ".*", "patternDescription": "anything" } } } }, "port": { "name": "Port", "type": "number", "integral": true, "description": "the default port for your Bitcoin node. default: 8333, testnet: 18333, regtest: 18444", "nullable": true, "default": 8333, "range": "[0, 9999]", "units": "m/s" }, "maxconnections": { "name": "Max Connections", "type": "string", "description": "the maximum number of commections allowed to your Bitcoin node", "nullable": true }, "rpcallowip": { "name": "RPC Allowed IPs", "type": "list", "subtype": "string", "description": "external ip addresses that are authorized to access your Bitcoin node", "changeWarning": "Any IP you allow here will have RPC access to your Bitcoin node.", "range": "[1,10]", "default": [ "192.168.1.1" ], "spec": { "type": "string", "pattern": "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))", "patternDescription": "must be a valid ipv4, ipv6, or domain name" } }, "rpcauth": { "name": "RPC Auth", "type": "list", "subtype": "string", "description": "api keys that are authorized to access your Bitcoin node.", "range": "[0,*)", "default": [], "spec": { "type": "string" } } }); let spec: ConfigSpec = serde_json::from_value(spec).unwrap(); spec.validate(todo!()).unwrap(); let config = spec .gen(&mut rand::rngs::StdRng::from_entropy(), &None) .unwrap(); spec.matches(&config).unwrap(); } }