systemprompt_config/bootstrap/secrets/vault/
error.rs1use systemprompt_traits::BoxedSource;
12
13pub(super) const MAX_VAULT_ERROR_CHARS: usize = 200;
14
15#[derive(Debug, thiserror::Error)]
16#[non_exhaustive]
17pub enum VaultError {
18 #[error("vault address is not a permitted outbound URL: {0}")]
19 Address(#[source] BoxedSource),
20
21 #[error("vault TLS CA certificate at {path} could not be loaded: {source}")]
22 CaCertificate {
23 path: String,
24 #[source]
25 source: BoxedSource,
26 },
27
28 #[error("vault HTTP client could not be built: {0}")]
29 ClientBuild(#[source] reqwest::Error),
30
31 #[error(
32 "vault credential is missing: {name}. Provide it in the process environment; never write \
33 a token into profile YAML."
34 )]
35 MissingCredential { name: String },
36
37 #[error("vault credential file {path} could not be read: {source}")]
38 CredentialFile {
39 path: String,
40 #[source]
41 source: std::io::Error,
42 },
43
44 #[error("vault credential file {path} could not be read: file is empty")]
45 EmptyCredentialFile { path: String },
46
47 #[error("vault {method} login was rejected with HTTP {status}{detail}")]
48 Auth {
49 method: &'static str,
50 status: u16,
51 detail: String,
52 },
53
54 #[error("vault denied access to {mount}/{path} (HTTP 403){detail}")]
55 Forbidden {
56 mount: String,
57 path: String,
58 detail: String,
59 },
60
61 #[error("vault has no secret at {mount}/{path}")]
62 NotFound { mount: String, path: String },
63
64 #[error("vault returned HTTP {status} for {mount}/{path}{detail}")]
65 Http {
66 status: u16,
67 mount: String,
68 path: String,
69 detail: String,
70 },
71
72 #[error("vault response body could not be read: {0}")]
73 Body(#[source] reqwest::Error),
74
75 #[error("vault response was not the expected KV v2 shape: {context}: {source}")]
76 Malformed {
77 context: String,
78 #[source]
79 source: serde_json::Error,
80 },
81
82 #[error(
83 "vault response was not the expected KV v2 shape: {location} has no field '{field}' for \
84 override key '{key}'"
85 )]
86 MissingField {
87 location: String,
88 field: String,
89 key: String,
90 },
91
92 #[error("vault request failed: {0}")]
93 Transport(#[source] reqwest::Error),
94
95 #[error("vault was unreachable after {attempts} attempt(s): {last}")]
96 Exhausted {
97 attempts: u32,
98 last: VaultAttemptFailure,
99 },
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
104pub enum VaultAttemptFailure {
105 #[error("HTTP {0}")]
106 Status(u16),
107
108 #[error("request timed out")]
109 Timeout,
110
111 #[error("could not connect")]
112 Connect,
113}
114
115#[must_use]
116pub(super) fn truncate_detail(raw: &str) -> String {
117 let trimmed = raw.trim();
118 if trimmed.is_empty() {
119 return String::new();
120 }
121 let clipped: String = trimmed.chars().take(MAX_VAULT_ERROR_CHARS).collect();
122 format!(": {clipped}")
123}