kernel/event/record/
provider.rs1use serde::{Deserialize, Serialize};
10
11use crate::error::AxError;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
20#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
21#[serde(untagged)]
22pub enum ProviderDegraded {
23 Refused(AxError),
25 VaultFellBack(VaultFellBack),
27}
28
29impl<'de> Deserialize<'de> for ProviderDegraded {
34 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
35 let line = serde_json::Value::deserialize(deserializer)?;
36 AxError::deserialize(&line)
37 .map(Self::Refused)
38 .or_else(|as_refusal| {
39 VaultFellBack::deserialize(&line)
40 .map(Self::VaultFellBack)
41 .map_err(|as_fallback| {
42 serde::de::Error::custom(format!(
43 "neither a provider refusal ({as_refusal}) nor a vault fallback \
44 ({as_fallback})"
45 ))
46 })
47 })
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
54pub struct VaultFellBack {
55 pub component: String,
57 pub fallback: String,
59 pub persistence: String,
61 pub reason: String,
63}
64
65#[cfg(test)]
66#[allow(clippy::unwrap_used, reason = "test code")]
67mod tests {
68 use super::*;
69 use crate::event::Payload;
70
71 #[test]
72 fn a_vault_line_keeps_the_bytes_its_ledger_already_holds() {
73 let line = r#"{"component":"vault","fallback":"session-memory","persistence":"this-process","reason":"probe reference unparsable"}"#;
74 let read: ProviderDegraded = serde_json::from_str::<Payload>(line)
75 .unwrap()
76 .read()
77 .unwrap();
78 assert!(matches!(read, ProviderDegraded::VaultFellBack(_)));
79 assert_eq!(
80 serde_json::to_string(&Payload::of(&read).unwrap()).unwrap(),
81 line
82 );
83 }
84
85 #[test]
86 fn a_line_that_is_neither_shape_names_what_each_shape_missed() {
87 let line = r#"{"code":"E_PROVIDER","component":"vault"}"#;
88 let err = serde_json::from_str::<Payload>(line)
89 .unwrap()
90 .read::<ProviderDegraded>()
91 .unwrap_err()
92 .to_string();
93 assert!(
94 err.contains("neither a provider refusal (missing field `action`)")
95 && err.contains("nor a vault fallback (missing field `fallback`)"),
96 "{err}"
97 );
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
104pub struct ProviderAccount {
105 pub id: crate::ServerLabel,
106 #[serde(default)]
107 #[cfg_attr(feature = "schema", schemars(with = "Option<String>"))]
108 pub reference: Option<crate::SecretRef>,
109 #[serde(default)]
110 pub header: Option<String>,
111}
112
113pub fn validate_provider_accounts(
118 accounts: Option<&[ProviderAccount]>,
119) -> Result<(), crate::AxError> {
120 let Some(accounts) = accounts else {
121 return Ok(());
122 };
123 let refuse = |subject: &str| {
124 crate::AxError::failure(
125 crate::AxCode::ConfigInvalid, "register provider accounts", subject,
126 ).with_recovery("give each account a distinct id and vault reference; use an explicit anonymous account for an unkeyed provider")
127 };
128 if accounts.is_empty() {
129 return Err(refuse("the account list is empty"));
130 }
131 let mut ids = std::collections::BTreeSet::new();
132 let mut references = std::collections::BTreeSet::new();
133 for account in accounts {
134 if !ids.insert(account.id.as_str()) {
135 return Err(refuse("an account id is repeated"));
136 }
137 if let Some(reference) = &account.reference {
138 if !references.insert(reference) {
139 return Err(refuse("a vault reference is repeated"));
140 }
141 } else if account.header.is_some() {
142 return Err(refuse(
143 "an anonymous account cannot carry a credential header",
144 ));
145 }
146 if let Some(header) = &account.header
147 && (header.is_empty()
148 || !header
149 .bytes()
150 .all(|byte| byte.is_ascii_alphanumeric() || b"!#$%&'*+-.^_`|~".contains(&byte)))
151 {
152 return Err(refuse("an account header name is invalid"));
153 }
154 }
155 Ok(())
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
160#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
161pub struct ProviderAccountBinding {
162 pub provider: String,
163 pub account: crate::ServerLabel,
164}