gel_config/
lib.rs

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        // For now, we'll ignore hints since we don't have a way to store them
117        // This could be extended later if needed
118        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
201/// Copied from edgeql-parser
202fn 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}