#[doc(hidden)]
pub mod meta {
#[doc(hidden)]
pub use const_format::concatcp;
#[derive(Clone, Copy)]
pub struct ConstSchema {
bytes: [u8; 65_536],
len: usize,
}
impl Default for ConstSchema {
fn default() -> Self {
Self::new()
}
}
impl ConstSchema {
pub const fn new() -> Self {
Self {
bytes: [0; 65_536],
len: 0,
}
}
pub const fn from_str(value: &str) -> Self {
Self::new().push_str(value)
}
#[must_use]
pub const fn push_str(mut self, value: &str) -> Self {
let value = value.as_bytes();
assert!(
self.len + value.len() <= self.bytes.len(),
"phoxal: const config schema exceeds 64 KiB"
);
let mut index = 0;
while index < value.len() {
self.bytes[self.len + index] = value[index];
index += 1;
}
self.len += value.len();
self
}
pub const fn as_str(&self) -> &str {
let (used, _) = self.bytes.split_at(self.len);
unsafe { core::str::from_utf8_unchecked(used) }
}
}
pub const fn bytes_of<const N: usize>(s: &str) -> [u8; N] {
let bytes = s.as_bytes();
assert!(
bytes.len() == N,
"phoxal: metadata manifest length mismatch"
);
let mut out = [0u8; N];
let mut i = 0;
while i < N {
out[i] = bytes[i];
i += 1;
}
out
}
}
pub trait ParticipantConfig: serde::de::DeserializeOwned + Send + 'static {
#[doc(hidden)]
const __SCHEMA: meta::ConstSchema;
const SCHEMA_JSON: &'static str = Self::__SCHEMA.as_str();
}
impl ParticipantConfig for () {
const __SCHEMA: meta::ConstSchema = meta::ConstSchema::from_str(r#"{"type":"null"}"#);
}
impl<T: ParticipantConfig> ParticipantConfig for Option<T> {
const __SCHEMA: meta::ConstSchema = meta::ConstSchema::new()
.push_str(r#"{"anyOf":["#)
.push_str(T::SCHEMA_JSON)
.push_str(r#",{"type":"null"}]}"#);
}
impl<T: ParticipantConfig> ParticipantConfig for Vec<T> {
const __SCHEMA: meta::ConstSchema = meta::ConstSchema::new()
.push_str(r#"{"type":"array","items":"#)
.push_str(T::SCHEMA_JSON)
.push_str("}");
}
impl<T: ParticipantConfig> ParticipantConfig for std::collections::BTreeMap<String, T> {
const __SCHEMA: meta::ConstSchema = meta::ConstSchema::new()
.push_str(r#"{"type":"object","additionalProperties":"#)
.push_str(T::SCHEMA_JSON)
.push_str("}");
}
impl<T: ParticipantConfig> ParticipantConfig for std::collections::HashMap<String, T> {
const __SCHEMA: meta::ConstSchema = meta::ConstSchema::new()
.push_str(r#"{"type":"object","additionalProperties":"#)
.push_str(T::SCHEMA_JSON)
.push_str("}");
}
macro_rules! primitive_config_schema {
($ty:ty => $schema:literal) => {
impl ParticipantConfig for $ty {
const __SCHEMA: meta::ConstSchema = meta::ConstSchema::from_str($schema);
}
};
}
primitive_config_schema!(bool => r#"{"type":"boolean"}"#);
primitive_config_schema!(String => r#"{"type":"string"}"#);
primitive_config_schema!(char => r#"{"type":"string","minLength":1,"maxLength":1}"#);
primitive_config_schema!(i8 => r#"{"type":"integer","format":"int8"}"#);
primitive_config_schema!(i16 => r#"{"type":"integer","format":"int16"}"#);
primitive_config_schema!(i32 => r#"{"type":"integer","format":"int32"}"#);
primitive_config_schema!(i64 => r#"{"type":"integer","format":"int64"}"#);
primitive_config_schema!(i128 => r#"{"type":"integer"}"#);
primitive_config_schema!(isize => r#"{"type":"integer"}"#);
primitive_config_schema!(u8 => r#"{"type":"integer","format":"uint8","minimum":0,"maximum":255}"#);
primitive_config_schema!(u16 => r#"{"type":"integer","format":"uint16","minimum":0,"maximum":65535}"#);
primitive_config_schema!(u32 => r#"{"type":"integer","format":"uint32","minimum":0}"#);
primitive_config_schema!(u64 => r#"{"type":"integer","format":"uint64","minimum":0}"#);
primitive_config_schema!(u128 => r#"{"type":"integer","minimum":0}"#);
primitive_config_schema!(usize => r#"{"type":"integer","minimum":0}"#);
primitive_config_schema!(f32 => r#"{"type":"number","format":"float"}"#);
primitive_config_schema!(f64 => r#"{"type":"number","format":"double"}"#);
#[cfg(test)]
mod tests {
use super::{ParticipantConfig, meta::ConstSchema};
#[test]
fn const_schema_exposes_only_the_pushed_prefix() {
let schema = ConstSchema::from_str(r#"{"type":"#)
.push_str(r#""string""#)
.push_str("}");
assert_eq!(schema.as_str(), r#"{"type":"string"}"#);
assert_eq!(ConstSchema::new().as_str(), "");
}
#[test]
fn blanket_config_schemas_compose_from_the_inner_schema() {
assert_eq!(<() as ParticipantConfig>::SCHEMA_JSON, r#"{"type":"null"}"#);
assert_eq!(
<Option<bool> as ParticipantConfig>::SCHEMA_JSON,
r#"{"anyOf":[{"type":"boolean"},{"type":"null"}]}"#
);
assert_eq!(
<Vec<String> as ParticipantConfig>::SCHEMA_JSON,
r#"{"type":"array","items":{"type":"string"}}"#
);
assert_eq!(
<std::collections::BTreeMap<String, u32> as ParticipantConfig>::SCHEMA_JSON,
r#"{"type":"object","additionalProperties":{"type":"integer","format":"uint32","minimum":0}}"#
);
}
}