1pub mod current;
2pub mod parser;
3pub mod schema;
4pub mod types;
5pub mod validation;
6
7use derive_more::{Display, Error};
8use indexmap::IndexMap;
9use std::{borrow::Cow, fmt::Debug};
10use toml::Value as TomlValue;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub enum PrimitiveType {
14 String,
15 Int64,
16 Int32,
17 Int16,
18 Float64,
19 Float32,
20 Boolean,
21 Duration,
22}
23
24impl PrimitiveType {
25 pub fn as_str(&self) -> &'static str {
26 match self {
27 PrimitiveType::String => "str",
28 PrimitiveType::Int64 => "int64",
29 PrimitiveType::Int32 => "int32",
30 PrimitiveType::Int16 => "int16",
31 PrimitiveType::Float64 => "float64",
32 PrimitiveType::Float32 => "float32",
33 PrimitiveType::Boolean => "bool",
34 PrimitiveType::Duration => "duration",
35 }
36 }
37
38 pub fn from_str(s: &str) -> Option<Self> {
39 match s {
40 "str" => Some(PrimitiveType::String),
41 "int64" => Some(PrimitiveType::Int64),
42 "int32" => Some(PrimitiveType::Int32),
43 "int16" => Some(PrimitiveType::Int16),
44 "float64" => Some(PrimitiveType::Float64),
45 "float32" => Some(PrimitiveType::Float32),
46 "bool" => Some(PrimitiveType::Boolean),
47 "duration" => Some(PrimitiveType::Duration),
48 _ => None,
49 }
50 }
51}
52
53impl std::fmt::Display for PrimitiveType {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(f, "{}", self.as_str())
56 }
57}
58
59#[derive(Clone)]
60pub enum Value {
61 Injected(String),
62 Set(Vec<Value>),
63 Array(Vec<Value>),
64 Insert {
65 typ: String,
66 values: IndexMap<String, Value>,
67 },
68}
69
70impl Debug for Value {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 match self {
73 Value::Injected(s) => write!(f, "{}", s),
74 Value::Set(values) => {
75 write!(f, "[")?;
76 for (i, value) in values.iter().enumerate() {
77 write!(f, "{:?}", value)?;
78 if i < values.len() - 1 {
79 write!(f, ", ")?;
80 }
81 }
82 write!(f, "]")
83 }
84 Value::Array(values) => {
85 write!(f, "[")?;
86 for (i, value) in values.iter().enumerate() {
87 write!(f, "{:?}", value)?;
88 if i < values.len() - 1 {
89 write!(f, ", ")?;
90 }
91 }
92 write!(f, "]")
93 }
94 Value::Insert { typ, values } => {
95 write!(f, "insert {} = {{\n", typ)?;
96 for (key, value) in values {
97 write!(f, " {}: {:?},\n", key, value)?;
98 }
99 write!(f, "}}")
100 }
101 }
102 }
103}
104
105pub trait HintExt {
106 fn with_hint<F>(self, hint: F) -> Self
107 where
108 F: FnOnce() -> String;
109}
110
111impl HintExt for ConfigError {
112 fn with_hint<F>(self, _hint: F) -> Self
113 where
114 F: FnOnce() -> String,
115 {
116 self
119 }
120}
121
122#[derive(Debug, Display, Error)]
123pub enum ConfigError {
124 #[display("expected a table for [local.config]")]
125 ExpectedTableForConfig,
126
127 #[display("unknown configuration option: {path}")]
128 UnknownConfigurationOption { path: String },
129
130 #[display("{path}: unknown config object: {object_ref}")]
131 UnknownConfigObject { path: String, object_ref: String },
132
133 #[display("{path} is missing _tname field")]
134 MissingTnameField { path: String },
135
136 #[display("{path}: unknown type {type_name}")]
137 UnknownType { path: String, type_name: String },
138
139 #[display("{path} expected {expected}, got {got}")]
140 TypeMismatch {
141 path: String,
142 expected: String,
143 got: String,
144 },
145
146 #[display("expected {expected}, got {got}")]
147 ExpectedGot { expected: String, got: String },
148}
149
150impl ConfigError {
151 pub fn expected_table_for_config() -> Self {
152 Self::ExpectedTableForConfig
153 }
154
155 pub fn unknown_configuration_option(path: String) -> Self {
156 Self::UnknownConfigurationOption { path }
157 }
158
159 pub fn unknown_config_object(path: String, object_ref: String) -> Self {
160 Self::UnknownConfigObject { path, object_ref }
161 }
162
163 pub fn missing_tname_field(path: String) -> Self {
164 Self::MissingTnameField { path }
165 }
166
167 pub fn unknown_type(path: String, type_name: String) -> Self {
168 Self::UnknownType { path, type_name }
169 }
170
171 pub fn type_mismatch(path: String, expected: String, got: String) -> Self {
172 Self::TypeMismatch {
173 path,
174 expected,
175 got,
176 }
177 }
178
179 pub fn expected_got(expected: String, got: String) -> Self {
180 Self::ExpectedGot { expected, got }
181 }
182
183 pub fn err_expected(
184 expected: impl std::fmt::Display,
185 got: &TomlValue,
186 path: &[String],
187 ) -> Self {
188 let got_str = match got {
189 TomlValue::String(s) => Cow::Owned(format!("\"{s}\"")),
190 TomlValue::Integer(_) => "an integer".into(),
191 TomlValue::Float(_) => "a float".into(),
192 TomlValue::Boolean(_) => "a boolean".into(),
193 TomlValue::Datetime(_) => "a datetime".into(),
194 TomlValue::Array(_) => "an array".into(),
195 TomlValue::Table(_) => "a table".into(),
196 };
197 Self::type_mismatch(path.join("."), expected.to_string(), got_str.to_string())
198 }
199}
200
201fn quote_string(s: &str) -> String {
203 use std::fmt::Write;
204
205 let mut buf = String::with_capacity(s.len() + 2);
206 buf.push('"');
207 for c in s.chars() {
208 match c {
209 '"' => {
210 buf.push('\\');
211 buf.push('"');
212 }
213 '\\' => {
214 buf.push('\\');
215 buf.push('\\');
216 }
217 '\x00'..='\x08'
218 | '\x0B'
219 | '\x0C'
220 | '\x0E'..='\x1F'
221 | '\u{007F}'
222 | '\u{0080}'..='\u{009F}' => {
223 write!(buf, "\\x{:02x}", c as u32).unwrap();
224 }
225 c => buf.push(c),
226 }
227 }
228 buf.push('"');
229 buf
230}