use std::fmt;
use std::marker::PhantomData;
use indexmap::IndexMap;
use serde::de::DeserializeOwned;
use serde_json::Value;
use super::builder::BuildError;
use super::policy::{FieldPolicy, FieldRequirement};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SettingsPathSegment {
Key(&'static str),
DynamicKey(String),
AnyKey,
AnyIndex,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct SettingsPathBuf {
segments: Vec<SettingsPathSegment>,
}
impl SettingsPathBuf {
pub fn new() -> Self {
Self::default()
}
pub fn from_key(key: &'static str) -> Self {
Self {
segments: vec![SettingsPathSegment::Key(key)],
}
}
pub fn from_segments(segments: impl IntoIterator<Item = SettingsPathSegment>) -> Self {
Self {
segments: segments.into_iter().collect(),
}
}
pub fn with_key(&self, key: &'static str) -> Self {
let mut path = self.clone();
path.segments.push(SettingsPathSegment::Key(key));
path
}
pub fn with_dynamic_key(&self, key: impl Into<String>) -> Self {
let mut path = self.clone();
path.segments
.push(SettingsPathSegment::DynamicKey(key.into()));
path
}
pub fn with_any_key(&self) -> Self {
let mut path = self.clone();
path.segments.push(SettingsPathSegment::AnyKey);
path
}
pub fn with_any_index(&self) -> Self {
let mut path = self.clone();
path.segments.push(SettingsPathSegment::AnyIndex);
path
}
pub fn extend(mut self, other: SettingsPathBuf) -> Self {
self.segments.extend(other.segments);
self
}
pub fn segments(&self) -> &[SettingsPathSegment] {
&self.segments
}
}
impl fmt::Display for SettingsPathBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (index, segment) in self.segments.iter().enumerate() {
if index > 0 {
f.write_str(".")?;
}
match segment {
SettingsPathSegment::Key(key) => f.write_str(key)?,
SettingsPathSegment::DynamicKey(key) => f.write_str(key)?,
SettingsPathSegment::AnyKey | SettingsPathSegment::AnyIndex => f.write_str("*")?,
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FieldRef<Root, Value> {
path: SettingsPathBuf,
_marker: PhantomData<fn() -> (Root, Value)>,
}
impl<Root, Value> FieldRef<Root, Value> {
pub fn new(path: SettingsPathBuf) -> Self {
Self {
path,
_marker: PhantomData,
}
}
pub fn path(&self) -> &SettingsPathBuf {
&self.path
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SecretFieldRef<Root, Value> {
path: SettingsPathBuf,
_marker: PhantomData<fn() -> (Root, Value)>,
}
impl<Root, Value> SecretFieldRef<Root, Value> {
pub fn new(path: SettingsPathBuf) -> Self {
Self {
path,
_marker: PhantomData,
}
}
pub fn path(&self) -> &SettingsPathBuf {
&self.path
}
pub fn erase_value(&self) -> SecretFieldRef<Root, ()> {
SecretFieldRef::new(self.path.clone())
}
}
#[derive(Clone, Debug)]
pub struct OptionalRef<Root, Value, SomeRef> {
path: SettingsPathBuf,
builder: fn(SettingsPathBuf) -> SomeRef,
_marker: PhantomData<fn() -> (Root, Value)>,
}
impl<Root, Value, SomeRef> OptionalRef<Root, Value, SomeRef> {
pub fn new(path: SettingsPathBuf, builder: fn(SettingsPathBuf) -> SomeRef) -> Self {
Self {
path,
builder,
_marker: PhantomData,
}
}
pub fn some(&self) -> SomeRef {
(self.builder)(self.path.clone())
}
pub fn path(&self) -> &SettingsPathBuf {
&self.path
}
}
impl<Root, Value, SomeRef> PartialEq for OptionalRef<Root, Value, SomeRef> {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl<Root, Value, SomeRef> Eq for OptionalRef<Root, Value, SomeRef> {}
#[derive(Clone, Debug)]
pub struct SequenceRef<Root, Value, ItemRef> {
path: SettingsPathBuf,
builder: fn(SettingsPathBuf) -> ItemRef,
_marker: PhantomData<fn() -> (Root, Value)>,
}
impl<Root, Value, ItemRef> SequenceRef<Root, Value, ItemRef> {
pub fn new(path: SettingsPathBuf, builder: fn(SettingsPathBuf) -> ItemRef) -> Self {
Self {
path,
builder,
_marker: PhantomData,
}
}
pub fn any(&self) -> ItemRef {
(self.builder)(self.path.with_any_index())
}
pub fn path(&self) -> &SettingsPathBuf {
&self.path
}
}
impl<Root, Value, ItemRef> PartialEq for SequenceRef<Root, Value, ItemRef> {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl<Root, Value, ItemRef> Eq for SequenceRef<Root, Value, ItemRef> {}
#[derive(Clone, Debug)]
pub struct MapRef<Root, Value, ItemRef> {
path: SettingsPathBuf,
builder: fn(SettingsPathBuf) -> ItemRef,
_marker: PhantomData<fn() -> (Root, Value)>,
}
impl<Root, Value, ItemRef> MapRef<Root, Value, ItemRef> {
pub fn new(path: SettingsPathBuf, builder: fn(SettingsPathBuf) -> ItemRef) -> Self {
Self {
path,
builder,
_marker: PhantomData,
}
}
pub fn any(&self) -> ItemRef {
(self.builder)(self.path.with_any_key())
}
pub fn entry(&self, key: impl Into<String>) -> ItemRef {
(self.builder)(self.path.with_dynamic_key(key))
}
pub fn path(&self) -> &SettingsPathBuf {
&self.path
}
}
impl<Root, Value, ItemRef> PartialEq for MapRef<Root, Value, ItemRef> {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl<Root, Value, ItemRef> Eq for MapRef<Root, Value, ItemRef> {}
#[derive(Clone, Debug)]
pub struct SettingsFieldSchema {
pub rust_name: &'static str,
pub key: &'static str,
pub policy: FieldPolicy,
pub value: SettingsValueSchema,
}
#[derive(Clone, Debug)]
pub enum SettingsValueSchema {
Leaf {
type_name: &'static str,
secret: bool,
},
Node {
type_name: &'static str,
node: fn(SettingsPathBuf) -> SettingsNodeSchema,
},
Optional {
inner: Box<SettingsValueSchema>,
},
Sequence {
inner: Box<SettingsValueSchema>,
},
Map {
inner: Box<SettingsValueSchema>,
},
}
impl SettingsValueSchema {
fn validate_required(
&self,
value: Option<&Value>,
path: SettingsPathBuf,
) -> Result<(), BuildError> {
match self {
SettingsValueSchema::Leaf { .. } => Ok(()),
SettingsValueSchema::Node { node, .. } => {
if let Some(map) = value.and_then(Value::as_object) {
node(path.clone()).validate_required_map_at(map, path)?;
}
Ok(())
}
SettingsValueSchema::Optional { inner } => {
if let Some(value) = value {
inner.validate_required(Some(value), path)?;
}
Ok(())
}
SettingsValueSchema::Sequence { inner } => {
if let Some(items) = value.and_then(Value::as_array) {
for (index, item) in items.iter().enumerate() {
inner.validate_required(
Some(item),
path.with_dynamic_key(index.to_string()),
)?;
}
}
Ok(())
}
SettingsValueSchema::Map { inner } => {
if let Some(entries) = value.and_then(Value::as_object) {
for (key, item) in entries {
inner.validate_required(Some(item), path.with_dynamic_key(key.clone()))?;
}
}
Ok(())
}
}
}
fn collect_secret_paths(&self, path: SettingsPathBuf, output: &mut Vec<SettingsPathBuf>) {
match self {
SettingsValueSchema::Leaf { secret, .. } => {
if *secret {
output.push(path);
}
}
SettingsValueSchema::Node { node, .. } => {
node(path.clone()).collect_secret_paths_at(path, output);
}
SettingsValueSchema::Optional { inner } => {
inner.collect_secret_paths(path, output);
}
SettingsValueSchema::Sequence { inner } => {
inner.collect_secret_paths(path.with_any_index(), output);
}
SettingsValueSchema::Map { inner } => {
inner.collect_secret_paths(path.with_any_key(), output);
}
}
}
}
#[derive(Clone, Debug)]
pub struct SettingsNodeSchema {
pub type_name: &'static str,
pub fields: Vec<SettingsFieldSchema>,
}
impl SettingsNodeSchema {
pub fn validate_required_map(
&self,
map: &serde_json::Map<String, Value>,
) -> Result<(), BuildError> {
self.validate_required_map_at(map, SettingsPathBuf::new())
}
pub fn validate_required_map_at(
&self,
map: &serde_json::Map<String, Value>,
base_path: SettingsPathBuf,
) -> Result<(), BuildError> {
self.validate_required_map_inner(map, base_path)
}
pub fn collect_secret_paths(&self, output: &mut Vec<SettingsPathBuf>) {
self.collect_secret_paths_at(SettingsPathBuf::new(), output);
}
fn validate_required_map_inner(
&self,
map: &serde_json::Map<String, Value>,
base_path: SettingsPathBuf,
) -> Result<(), BuildError> {
for field in &self.fields {
let path = base_path.with_key(field.key);
let value = map.get(field.key);
if field.policy.requirement == FieldRequirement::Required && value.is_none() {
return Err(BuildError::MissingRequiredPath { path });
}
field.value.validate_required(value, path)?;
}
Ok(())
}
fn collect_secret_paths_at(
&self,
base_path: SettingsPathBuf,
output: &mut Vec<SettingsPathBuf>,
) {
for field in &self.fields {
field
.value
.collect_secret_paths(base_path.with_key(field.key), output);
}
}
}
pub trait SettingsNode:
Clone + fmt::Debug + serde::Serialize + DeserializeOwned + Send + Sync + 'static
{
type Schema<Root>;
fn schema_at<Root>(path: SettingsPathBuf) -> Self::Schema<Root>;
fn node_schema() -> SettingsNodeSchema;
}
pub trait HasSettingsSchema {
type Schema;
fn schema() -> Self::Schema;
fn settings_schema() -> Self::Schema {
Self::schema()
}
}
#[doc(hidden)]
pub fn root_section<'a>(
merged: &'a IndexMap<String, Value>,
primary_key: &'static str,
fallback_key: &'static str,
) -> Option<&'a serde_json::Map<String, Value>> {
merged
.get(primary_key)
.or_else(|| {
if primary_key == fallback_key {
None
} else {
merged.get(fallback_key)
}
})
.and_then(Value::as_object)
}
#[cfg(test)]
mod tests {
use indexmap::IndexMap;
use serde_json::{Value, json};
use super::root_section;
#[test]
fn root_section_primary_object_wins_over_fallback_object() {
let mut merged = IndexMap::new();
merged.insert("primary".to_string(), json!({ "source": "primary" }));
merged.insert("fallback".to_string(), json!({ "source": "fallback" }));
let section = root_section(&merged, "primary", "fallback").expect("primary object");
assert_eq!(
section.get("source"),
Some(&Value::String("primary".into()))
);
}
#[test]
fn root_section_uses_fallback_object_when_primary_absent() {
let mut merged = IndexMap::new();
merged.insert("fallback".to_string(), json!({ "source": "fallback" }));
let section = root_section(&merged, "primary", "fallback").expect("fallback object");
assert_eq!(
section.get("source"),
Some(&Value::String("fallback".into()))
);
}
#[test]
fn root_section_malformed_primary_scalar_does_not_fall_back() {
let mut merged = IndexMap::new();
merged.insert("primary".to_string(), json!("malformed"));
merged.insert("fallback".to_string(), json!({ "source": "fallback" }));
let section = root_section(&merged, "primary", "fallback");
assert!(section.is_none());
}
}