ts_config/
load.rs

1//! Load a config file
2
3use schemars::{SchemaGenerator, generate::SchemaSettings};
4use ts_error::diagnostic::Diagnostics;
5use ts_io::{ReadFileError, read_file_to_string};
6use ts_json::{ValidationError, validate};
7
8use crate::ConfigFile;
9
10/// Error variants for loading config.
11#[derive(Debug)]
12#[non_exhaustive]
13#[allow(missing_docs)]
14pub enum LoadConfigError {
15    #[non_exhaustive]
16    SerailizeSchema { source: serde_json::Error },
17
18    #[non_exhaustive]
19    ValidationFailure { source: ValidationError },
20
21    #[non_exhaustive]
22    InvalidConfig { source: Diagnostics },
23
24    #[non_exhaustive]
25    DeserializeConfig { source: serde_json::Error },
26
27    #[non_exhaustive]
28    ReadConfig { source: ReadFileError },
29}
30impl core::fmt::Display for LoadConfigError {
31    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
32        match &self {
33            Self::SerailizeSchema { .. } => {
34                write!(f, "JSON schema for the config could not be serialized")
35            }
36            Self::ValidationFailure { .. } => write!(f, "could not validate config file"),
37            Self::InvalidConfig { .. } => write!(f, "config file is invalid"),
38            Self::DeserializeConfig { .. } => write!(f, "config file could not be deserialized"),
39            Self::ReadConfig { .. } => write!(f, "could not read config file"),
40        }
41    }
42}
43impl core::error::Error for LoadConfigError {
44    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
45        match &self {
46            Self::DeserializeConfig { source, .. } | Self::SerailizeSchema { source, .. } => {
47                Some(source)
48            }
49            Self::ValidationFailure { source, .. } => Some(source),
50            Self::InvalidConfig { source, .. } => Some(source),
51            Self::ReadConfig { source, .. } => Some(source),
52        }
53    }
54}
55
56/// Try load a config file, linting it against its JSON schema.
57pub fn try_load<C: ConfigFile>() -> Result<C, LoadConfigError> {
58    let source = read_file_to_string(&C::config_file_path())
59        .map_err(|source| LoadConfigError::ReadConfig { source })?;
60
61    let schema_generator = SchemaGenerator::from(SchemaSettings::draft07());
62    let schema = schema_generator.into_root_schema_for::<C>();
63    let schema = serde_json::to_string(&schema)
64        .map_err(|source| LoadConfigError::SerailizeSchema { source })?;
65
66    let diagnostics = validate(&source, &schema, Some(C::config_file_path()).as_deref())
67        .map_err(|source| LoadConfigError::ValidationFailure { source })?;
68
69    if !diagnostics.is_empty() {
70        Err(LoadConfigError::InvalidConfig {
71            source: diagnostics,
72        })
73    } else {
74        serde_json::from_str(&source)
75            .map_err(|source| LoadConfigError::DeserializeConfig { source })
76    }
77}