schematic/
format.rs

1use crate::helpers::extract_ext;
2use miette::Diagnostic;
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6/// Supported source configuration formats.
7#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
8#[serde(rename_all = "lowercase")]
9pub enum Format {
10    // This is to simply handle the use case when no features are
11    // enabled. If this doesn't exist, Rust errors with no variants.
12    #[doc(hidden)]
13    #[default]
14    None,
15
16    #[cfg(feature = "json")]
17    Json,
18
19    #[cfg(feature = "pkl")]
20    Pkl,
21
22    #[cfg(feature = "ron")]
23    Ron,
24
25    #[cfg(feature = "toml")]
26    Toml,
27
28    #[cfg(any(feature = "yaml", feature = "yml"))]
29    Yaml,
30}
31
32impl Format {
33    /// Detects a format from a provided value, either a file path or URL, by
34    /// checking for a supported file extension.
35    pub fn detect(value: &str) -> Result<Format, UnsupportedFormatError> {
36        let mut available: Vec<&str> = vec![];
37        let ext = extract_ext(value).unwrap_or_default();
38
39        #[cfg(feature = "json")]
40        {
41            available.push("JSON");
42
43            if ext == ".json" {
44                return Ok(Format::Json);
45            }
46        }
47
48        #[cfg(feature = "pkl")]
49        {
50            available.push("Pkl");
51
52            if ext == ".pkl" {
53                return Ok(Format::Pkl);
54            }
55        }
56
57        #[cfg(feature = "ron")]
58        {
59            available.push("RON");
60
61            if ext == ".ron" {
62                return Ok(Format::Ron);
63            }
64        }
65
66        #[cfg(feature = "toml")]
67        {
68            available.push("TOML");
69
70            if ext == ".toml" {
71                return Ok(Format::Toml);
72            }
73        }
74
75        #[cfg(any(feature = "yaml", feature = "yml"))]
76        {
77            available.push("YAML");
78
79            if ext == ".yaml" || ext == ".yml" {
80                return Ok(Format::Yaml);
81            }
82        }
83
84        Err(UnsupportedFormatError(
85            value.to_owned(),
86            available.join(", "),
87        ))
88    }
89
90    pub fn is_json(&self) -> bool {
91        #[cfg(feature = "json")]
92        {
93            matches!(self, Format::Json)
94        }
95        #[cfg(not(feature = "json"))]
96        {
97            false
98        }
99    }
100
101    pub fn is_pkl(&self) -> bool {
102        #[cfg(feature = "pkl")]
103        {
104            matches!(self, Format::Pkl)
105        }
106        #[cfg(not(feature = "pkl"))]
107        {
108            false
109        }
110    }
111
112    pub fn is_ron(&self) -> bool {
113        #[cfg(feature = "ron")]
114        {
115            matches!(self, Format::Ron)
116        }
117        #[cfg(not(feature = "ron"))]
118        {
119            false
120        }
121    }
122
123    pub fn is_toml(&self) -> bool {
124        #[cfg(feature = "toml")]
125        {
126            matches!(self, Format::Toml)
127        }
128        #[cfg(not(feature = "toml"))]
129        {
130            false
131        }
132    }
133
134    pub fn is_yaml(&self) -> bool {
135        #[cfg(any(feature = "yaml", feature = "yml"))]
136        {
137            matches!(self, Format::Yaml)
138        }
139        #[cfg(not(any(feature = "yaml", feature = "yml")))]
140        {
141            false
142        }
143    }
144}
145
146#[derive(Clone, Debug, Diagnostic, Error)]
147#[diagnostic(code(config::format::unsupported))]
148#[error("Unsupported format for {0}, expected {1}.")]
149pub struct UnsupportedFormatError(String, String);