1use std::borrow::Cow;
2use std::fmt;
3
4use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
5use serde::{Deserialize, Serialize};
6
7use crate::adapter::SECRET_MARKER;
8
9#[derive(Clone, PartialEq, Eq)]
20pub enum Secret {
21 Value(String),
23 Env(String),
25}
26
27impl Secret {
28 pub fn env(var: impl Into<String>) -> Self {
30 Secret::Env(var.into())
31 }
32
33 fn read(&self) -> Result<String, ResolveError> {
36 match self {
37 Secret::Value(value) => Ok(value.clone()),
38 Secret::Env(var) => {
39 std::env::var(var).map_err(|_| ResolveError::EnvNotSet(var.clone()))
40 }
41 }
42 }
43
44 pub fn resolve(&self, override_var: &str) -> Result<String, ResolveError> {
49 resolve_required(self, override_var)
50 }
51
52 pub fn resolve_optional(
55 secret: Option<&Secret>,
56 override_var: &str,
57 ) -> Result<Option<String>, ResolveError> {
58 resolve_optional(secret, override_var)
59 }
60}
61
62impl JsonSchema for Secret {
65 fn schema_name() -> Cow<'static, str> {
66 Cow::Borrowed("Secret")
67 }
68
69 fn json_schema(_: &mut SchemaGenerator) -> Schema {
70 let mut schema = json_schema!({
71 "description": "A literal value, or `{ env = \"VAR\" }` to read it from the environment at run time.",
72 "anyOf": [
73 { "type": "string" },
74 {
75 "type": "object",
76 "properties": { "env": { "type": "string", "description": "The environment variable to read." } },
77 "required": ["env"],
78 "additionalProperties": false
79 }
80 ]
81 });
82 schema.insert(SECRET_MARKER.to_owned(), serde_json::Value::Bool(true));
83 schema
84 }
85}
86
87impl From<&str> for Secret {
88 fn from(value: &str) -> Self {
89 Secret::Value(value.to_owned())
90 }
91}
92
93impl From<String> for Secret {
94 fn from(value: String) -> Self {
95 Secret::Value(value)
96 }
97}
98
99const EXPECTED: &str = "a string value or an env reference `{ env = \"VAR\" }`";
101
102impl Serialize for Secret {
103 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
104 use serde::ser::SerializeMap;
105 match self {
106 Secret::Value(value) => serializer.serialize_str(value),
107 Secret::Env(env) => {
108 let mut map = serializer.serialize_map(Some(1))?;
109 map.serialize_entry("env", env)?;
110 map.end()
111 }
112 }
113 }
114}
115
116struct SecretVisitor;
117
118impl<'de> serde::de::Visitor<'de> for SecretVisitor {
119 type Value = Secret;
120
121 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 f.write_str(EXPECTED)
123 }
124
125 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
126 Ok(Secret::Value(value.to_owned()))
127 }
128
129 fn visit_string<E: serde::de::Error>(self, value: String) -> Result<Self::Value, E> {
130 Ok(Secret::Value(value))
131 }
132
133 fn visit_map<A: serde::de::MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
134 use serde::de::Error;
135 let Some(key) = map.next_key::<String>()? else {
136 return Err(A::Error::custom(format!(
137 "expected {EXPECTED}, found an empty table"
138 )));
139 };
140 if key != "env" {
141 return Err(A::Error::custom(format!(
142 "unknown key `{key}` — expected {EXPECTED}"
143 )));
144 }
145 let env: String = map.next_value()?;
146 if let Some(extra) = map.next_key::<String>()? {
147 return Err(A::Error::custom(format!(
148 "unexpected key `{extra}` in env reference — write it as `{{ env = \"VAR\" }}`"
149 )));
150 }
151 Ok(Secret::Env(env))
152 }
153}
154
155impl<'de> Deserialize<'de> for Secret {
156 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
157 deserializer.deserialize_any(SecretVisitor)
158 }
159}
160
161impl fmt::Debug for Secret {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 match self {
166 Secret::Value(_) => write!(f, "Secret(***)"),
167 Secret::Env(var) => write!(f, "Secret(env {var})"),
168 }
169 }
170}
171
172impl fmt::Display for Secret {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 match self {
175 Secret::Value(_) => write!(f, "***"),
176 Secret::Env(var) => write!(f, "${{{var}}}"),
177 }
178 }
179}
180
181fn resolve_required(secret: &Secret, reserved: &str) -> Result<String, ResolveError> {
182 match secret {
183 env @ Secret::Env(_) => env.read(),
184 Secret::Value(literal) => Ok(literal_or_override(literal, reserved)),
185 }
186}
187
188fn resolve_optional(
189 secret: Option<&Secret>,
190 reserved: &str,
191) -> Result<Option<String>, ResolveError> {
192 match secret {
193 Some(env @ Secret::Env(_)) => env.read().map(Some),
194 Some(Secret::Value(literal)) => Ok(Some(literal_or_override(literal, reserved))),
195 None => Ok(std::env::var(reserved).ok()),
196 }
197}
198
199fn literal_or_override(literal: &str, reserved: &str) -> String {
201 std::env::var(reserved).unwrap_or_else(|_| literal.to_owned())
202}
203
204#[derive(thiserror::Error, Debug)]
206pub enum ResolveError {
207 #[error("environment variable '{0}' is not set")]
208 EnvNotSet(String),
209 #[error("{0}")]
212 Missing(String),
213 #[error("{0}")]
216 Invalid(String),
217}