1use 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#[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
56pub 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}