use std::marker::PhantomData;
use std::path::Path;
use confique::Config;
use confique::meta::{self, Expr};
use serde::Deserialize;
use toml::Table;
use crate::error::ClapfigError;
use crate::runtime::{self, LeafType, Schema};
#[derive(Clone, Copy)]
pub(crate) enum SchemaRef<'a> {
Static { meta: &'a meta::Meta },
Dynamic { schema: &'a Schema },
}
impl<'a> SchemaRef<'a> {
pub fn from_meta(meta: &'a meta::Meta) -> Self {
SchemaRef::Static { meta }
}
pub fn from_dynamic(schema: &'a Schema) -> Self {
SchemaRef::Dynamic { schema }
}
pub fn name(&self) -> &'a str {
match self {
SchemaRef::Static { meta } => meta.name,
SchemaRef::Dynamic { schema } => schema.name.as_str(),
}
}
pub fn doc(&self) -> DocSource<'a> {
match self {
SchemaRef::Static { meta } => DocSource::Static(meta.doc),
SchemaRef::Dynamic { schema } => DocSource::Dynamic(&schema.doc),
}
}
pub fn strict(&self) -> Option<bool> {
match self {
SchemaRef::Static { .. } => None,
SchemaRef::Dynamic { schema } => schema.strict,
}
}
pub fn fields(&self) -> SchemaFieldsIter<'a> {
match self {
SchemaRef::Static { meta } => SchemaFieldsIter::Static {
fields: meta.fields,
index: 0,
},
SchemaRef::Dynamic { schema } => SchemaFieldsIter::Dynamic {
fields: &schema.fields,
index: 0,
},
}
}
}
pub(crate) enum SchemaFieldsIter<'a> {
Static {
fields: &'a [meta::Field],
index: usize,
},
Dynamic {
fields: &'a [runtime::NamedField],
index: usize,
},
}
impl<'a> Iterator for SchemaFieldsIter<'a> {
type Item = FieldRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
match self {
SchemaFieldsIter::Static { fields, index } => {
if *index >= fields.len() {
return None;
}
let f = &fields[*index];
*index += 1;
Some(FieldRef::from_static(f))
}
SchemaFieldsIter::Dynamic { fields, index } => {
if *index >= fields.len() {
return None;
}
let f = &fields[*index];
*index += 1;
Some(FieldRef::from_dynamic(f))
}
}
}
}
#[derive(Clone, Copy)]
pub(crate) struct FieldRef<'a> {
pub name: &'a str,
pub doc: DocSource<'a>,
pub kind: FieldKindRef<'a>,
}
impl<'a> FieldRef<'a> {
fn from_static(f: &'a meta::Field) -> Self {
let kind = match &f.kind {
meta::FieldKind::Leaf { env, kind } => {
let (default, optional) = match kind {
meta::LeafKind::Required { default } => (default.as_ref(), false),
meta::LeafKind::Optional => (None, true),
};
FieldKindRef::Leaf(LeafRef {
default: default.map(LeafDefault::Expr),
env: *env,
optional,
allowed_values: None,
ty: None,
})
}
meta::FieldKind::Nested { meta } => FieldKindRef::Nested {
schema: SchemaRef::from_meta(meta),
},
};
FieldRef {
name: f.name,
doc: DocSource::Static(f.doc),
kind,
}
}
fn from_dynamic(f: &'a runtime::NamedField) -> Self {
let (doc_source, kind) = match &f.field {
runtime::Field::Leaf(leaf) => {
let allowed_values = match &leaf.ty {
LeafType::Enum { values } => Some(values.as_slice()),
_ => None,
};
let leaf_ref = LeafRef {
default: leaf.default.as_ref().map(LeafDefault::Toml),
env: leaf.env.as_deref(),
optional: leaf.optional,
allowed_values,
ty: Some(&leaf.ty),
};
(DocSource::Dynamic(&leaf.doc), FieldKindRef::Leaf(leaf_ref))
}
runtime::Field::Nested(schema) => (
DocSource::Dynamic(&schema.doc),
FieldKindRef::Nested {
schema: SchemaRef::from_dynamic(schema),
},
),
runtime::Field::ArrayOf(schema) => (
DocSource::Dynamic(&schema.doc),
FieldKindRef::ArrayOf {
schema: SchemaRef::from_dynamic(schema),
},
),
runtime::Field::MapOf(schema) => (
DocSource::Dynamic(&schema.doc),
FieldKindRef::MapOf {
schema: SchemaRef::from_dynamic(schema),
},
),
};
FieldRef {
name: f.name.as_str(),
doc: doc_source,
kind,
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum FieldKindRef<'a> {
Leaf(LeafRef<'a>),
Nested {
schema: SchemaRef<'a>,
},
ArrayOf {
schema: SchemaRef<'a>,
},
MapOf {
schema: SchemaRef<'a>,
},
}
#[derive(Clone, Copy)]
pub(crate) struct LeafRef<'a> {
pub default: Option<LeafDefault<'a>>,
pub env: Option<&'a str>,
pub optional: bool,
pub allowed_values: Option<&'a [toml::Value]>,
pub ty: Option<&'a LeafType>,
}
#[derive(Clone, Copy)]
pub(crate) enum LeafDefault<'a> {
Expr(&'a Expr),
Toml(&'a toml::Value),
}
#[derive(Clone, Copy)]
pub(crate) enum DocSource<'a> {
Static(&'a [&'a str]),
Dynamic(&'a [String]),
}
impl<'a> DocSource<'a> {
pub fn iter(&self) -> DocLines<'a> {
match self {
DocSource::Static(lines) => DocLines::Static(lines.iter()),
DocSource::Dynamic(lines) => DocLines::Dynamic(lines.iter()),
}
}
pub fn is_empty(&self) -> bool {
match self {
DocSource::Static(lines) => lines.is_empty(),
DocSource::Dynamic(lines) => lines.is_empty(),
}
}
}
pub(crate) enum DocLines<'a> {
Static(std::slice::Iter<'a, &'a str>),
Dynamic(std::slice::Iter<'a, String>),
}
impl<'a> Iterator for DocLines<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
match self {
DocLines::Static(it) => it.next().copied(),
DocLines::Dynamic(it) => it.next().map(|s| s.as_str()),
}
}
}
pub(crate) trait ConfigSpec {
type Output;
fn schema(&self) -> SchemaRef<'_>;
fn validate_unknown(
&self,
table: &Table,
source: &str,
path: &Path,
ctx: &crate::validate::ValidateContext<'_>,
) -> Result<Vec<crate::strict::CollectedUnknown>, ClapfigError>;
fn fill_defaults(&self, _table: &mut Table) -> Result<(), ClapfigError> {
Ok(())
}
fn finalize(&self, merged: Table) -> Result<Self::Output, ClapfigError>;
}
pub(crate) struct StaticSpec<C> {
_phantom: PhantomData<fn() -> C>,
}
impl<C> StaticSpec<C> {
pub const fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<C> ConfigSpec for StaticSpec<C>
where
C: Config,
C::Layer: for<'de> Deserialize<'de>,
{
type Output = C;
fn schema(&self) -> SchemaRef<'_> {
SchemaRef::from_meta(&C::META)
}
fn validate_unknown(
&self,
table: &Table,
source: &str,
path: &Path,
ctx: &crate::validate::ValidateContext<'_>,
) -> Result<Vec<crate::strict::CollectedUnknown>, ClapfigError> {
crate::validate::validate_unknown_keys::<C>(table, source, path, ctx)
}
fn finalize(&self, merged: Table) -> Result<C, ClapfigError> {
let layer: C::Layer =
toml::Value::Table(merged)
.try_into()
.map_err(|e: toml::de::Error| ClapfigError::InvalidValue {
key: "<merged>".into(),
reason: e.to_string(),
})?;
C::builder()
.preloaded(layer)
.load()
.map_err(ClapfigError::from)
}
}