ts_config/
load.rs

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