1use crate::alloc_prelude::*;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
7pub enum Casing {
8 #[default]
10 #[cfg_attr(feature = "serde", serde(rename = "camelCase"))]
11 CamelCase,
12 #[cfg_attr(feature = "serde", serde(rename = "snake_case"))]
14 SnakeCase,
15}
16
17impl Casing {
18 #[must_use]
19 pub const fn as_str(self) -> &'static str {
20 match self {
21 Self::CamelCase => "camelCase",
22 Self::SnakeCase => "snake_case",
23 }
24 }
25}
26
27impl core::fmt::Display for Casing {
28 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29 f.write_str(self.as_str())
30 }
31}
32
33#[cfg(any(feature = "std", feature = "alloc"))]
34impl core::str::FromStr for Casing {
35 type Err = crate::alloc_prelude::String;
36
37 fn from_str(s: &str) -> Result<Self, Self::Err> {
38 match s {
39 "camelCase" | "camel" => Ok(Self::CamelCase),
40 "snake_case" | "snake" => Ok(Self::SnakeCase),
41 _ => Err(format!(
42 "invalid casing '{s}', expected 'camelCase' or 'snake_case'"
43 )),
44 }
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Hash)]
53pub struct MigrationTracking {
54 pub table: Cow<'static, str>,
56 pub schema: Option<Cow<'static, str>>,
58}
59
60impl MigrationTracking {
61 pub const SQLITE: Self = Self {
63 table: Cow::Borrowed("__drizzle_migrations"),
64 schema: None,
65 };
66
67 pub const POSTGRES: Self = Self {
69 table: Cow::Borrowed("__drizzle_migrations"),
70 schema: Some(Cow::Borrowed("drizzle")),
71 };
72
73 pub const MYSQL: Self = Self {
75 table: Cow::Borrowed("__drizzle_migrations"),
76 schema: None,
77 };
78
79 pub fn new(
81 table: impl Into<Cow<'static, str>>,
82 schema: Option<impl Into<Cow<'static, str>>>,
83 ) -> Self {
84 Self {
85 table: table.into(),
86 schema: schema.map(Into::into),
87 }
88 }
89
90 #[must_use]
92 pub fn table(mut self, table: impl Into<Cow<'static, str>>) -> Self {
93 self.table = table.into();
94 self
95 }
96
97 #[must_use]
99 pub fn schema(mut self, schema: impl Into<Cow<'static, str>>) -> Self {
100 self.schema = Some(schema.into());
101 self
102 }
103
104 #[must_use]
106 pub fn without_schema(mut self) -> Self {
107 self.schema = None;
108 self
109 }
110}
111impl Default for MigrationTracking {
112 fn default() -> Self {
113 Self::SQLITE
114 }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
124pub enum ConfigValue {
125 Inline(String),
127 Env(String),
129}
130
131#[cfg(feature = "std")]
132impl ConfigValue {
133 pub fn resolve(&self) -> Result<String, ConfigValueError> {
141 match self {
142 Self::Inline(v) => Ok(v.clone()),
143 Self::Env(var) => match std::env::var(var) {
144 Ok(v) => Ok(v),
145 Err(std::env::VarError::NotPresent) => {
146 Err(ConfigValueError::NotPresent(var.clone()))
147 }
148 Err(std::env::VarError::NotUnicode(_)) => {
149 Err(ConfigValueError::NotUnicode(var.clone()))
150 }
151 },
152 }
153 }
154
155 pub fn resolve_optional(&self) -> Result<Option<String>, ConfigValueError> {
162 match self {
163 Self::Inline(v) => Ok(Some(v.clone())),
164 Self::Env(var) => match std::env::var(var) {
165 Ok(v) => Ok(Some(v)),
166 Err(std::env::VarError::NotPresent) => Ok(None),
167 Err(std::env::VarError::NotUnicode(_)) => {
168 Err(ConfigValueError::NotUnicode(var.clone()))
169 }
170 },
171 }
172 }
173}
174
175#[cfg(feature = "std")]
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub enum ConfigValueError {
179 NotPresent(String),
181 NotUnicode(String),
183}
184
185#[cfg(feature = "std")]
186impl core::fmt::Display for ConfigValueError {
187 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
188 match self {
189 Self::NotPresent(var) => write!(f, "env var `{var}` not set"),
190 Self::NotUnicode(var) => write!(f, "env var `{var}` contains invalid unicode"),
191 }
192 }
193}
194
195#[cfg(feature = "std")]
196impl std::error::Error for ConfigValueError {}
197
198#[cfg(feature = "serde")]
199impl<'de> serde::Deserialize<'de> for ConfigValue {
200 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
201 where
202 D: serde::Deserializer<'de>,
203 {
204 use serde::de::{self, MapAccess, Visitor};
205
206 struct ConfigValueVisitor;
207
208 impl<'de> Visitor<'de> for ConfigValueVisitor {
209 type Value = ConfigValue;
210
211 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
212 formatter.write_str("a string or { env = \"VAR_NAME\" }")
213 }
214
215 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
216 where
217 E: de::Error,
218 {
219 Ok(ConfigValue::Inline(value.to_string()))
220 }
221
222 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
223 where
224 M: MapAccess<'de>,
225 {
226 let mut env_var: Option<String> = None;
227
228 while let Some(key) = map.next_key::<String>()? {
229 if key == "env" {
230 env_var = Some(map.next_value()?);
231 } else {
232 return Err(de::Error::unknown_field(&key, &["env"]));
233 }
234 }
235
236 env_var
237 .map(ConfigValue::Env)
238 .ok_or_else(|| de::Error::missing_field("env"))
239 }
240 }
241
242 deserializer.deserialize_any(ConfigValueVisitor)
243 }
244}
245
246#[cfg(feature = "schemars")]
247impl schemars::JsonSchema for ConfigValue {
248 fn schema_name() -> Cow<'static, str> {
249 "ConfigValue".into()
250 }
251
252 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
253 use schemars::json_schema;
254
255 json_schema!({
257 "oneOf": [
258 generator.subschema_for::<String>(),
259 {
260 "type": "object",
261 "properties": {
262 "env": { "type": "string" }
263 },
264 "required": ["env"],
265 "additionalProperties": false
266 }
267 ]
268 })
269 }
270}