use std::path::{Path, PathBuf};
use crate::Product;
pub const PULLED_NEVER_DIALLED: &str = "http://pulled.invalid:1";
#[derive(Clone, Copy, Debug)]
pub struct ProductBuild<'a> {
pub id: &'a str,
pub endpoint: &'a str,
pub identity_key: Option<&'a Path>,
pub appliance_key: Option<&'a str>,
pub bearer_token: Option<&'a str>,
pub api_key_env: Option<&'a str>,
pub restoring_from_twin: Option<&'a str>,
pub tls_ca: Option<&'a std::path::Path>,
pub tls_client: Option<(&'a std::path::Path, &'a std::path::Path)>,
pub guest: Option<&'a GuestSpec>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct GuestSpec {
pub module: PathBuf,
pub digest: Option<String>,
pub trust_roots: Vec<PathBuf>,
pub grants: Vec<String>,
pub http_hosts: Vec<String>,
pub grpc_hosts: Vec<String>,
pub fuel: Option<u64>,
pub call_timeout_ms: Option<u64>,
pub max_memory_bytes: Option<u64>,
pub http_auth: Vec<HttpAuth>,
pub hmac_secrets: Vec<String>,
pub secrets_dir: Option<PathBuf>,
pub plugin: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct HttpAuth {
pub host: String,
pub header: Option<String>,
pub prefix: String,
pub secret: Option<String>,
pub basic_user: Option<String>,
pub basic_user_secret: Option<String>,
pub client_cert_secret: Option<String>,
pub body_secrets: Vec<String>,
}
impl HttpAuth {
pub fn secret_names(&self) -> Vec<&str> {
[&self.secret, &self.basic_user_secret, &self.client_cert_secret]
.into_iter()
.flatten()
.map(String::as_str)
.chain(self.body_secrets.iter().map(String::as_str))
.collect()
}
}
pub fn body_secret_placeholder(name: &str) -> String {
format!("{{{{secret:{name}}}}}")
}
impl ProductBuild<'_> {
pub fn err(&self, what: impl std::fmt::Display) -> String {
format!("[products.{}] {what}", self.id)
}
}
pub type ProductBuilder = fn(&ProductBuild<'_>) -> Result<Box<dyn Product>, String>;
pub type IdentityMinter = fn(&ProductBuild<'_>) -> Result<String, String>;
#[derive(Clone, Copy)]
pub struct ProductFactory {
pub name: &'static str,
pub build: ProductBuilder,
pub mint_identity: IdentityMinter,
pub guardian: Option<&'static dyn crate::guardian::EstateGuardian>,
}
impl std::fmt::Debug for ProductFactory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProductFactory").field("name", &self.name).finish_non_exhaustive()
}
}
pub fn register(list: &mut Vec<ProductFactory>, f: ProductFactory) {
if let Some(slot) = list.iter_mut().find(|g| g.name == f.name) {
*slot = f;
} else {
list.push(f);
}
}
pub fn find<'a>(list: &'a [ProductFactory], name: &str) -> Option<&'a ProductFactory> {
list.iter().find(|f| f.name == name)
}
#[cfg(test)]
mod tests {
use super::*;
fn refuse(b: &ProductBuild<'_>) -> Result<Box<dyn Product>, String> {
Err(b.err("no"))
}
fn no_identity(b: &ProductBuild<'_>) -> Result<String, String> {
Err(b.err("nothing to mint"))
}
#[test]
fn register_replaces_a_name_and_find_answers_by_name() {
let mut list = Vec::new();
register(&mut list, ProductFactory { name: "a", build: refuse, mint_identity: no_identity, guardian: None });
register(&mut list, ProductFactory { name: "b", build: refuse, mint_identity: no_identity, guardian: None });
register(&mut list, ProductFactory { name: "a", build: refuse, mint_identity: no_identity, guardian: None });
assert_eq!(list.len(), 2, "a second registration of a name replaces, never duplicates");
assert!(find(&list, "a").is_some() && find(&list, "b").is_some());
assert!(find(&list, "c").is_none());
}
#[test]
fn a_refusal_names_the_block() {
let b = ProductBuild { id: "acme", endpoint: "x", identity_key: None, appliance_key: None, bearer_token: None, api_key_env: None, restoring_from_twin: None, tls_ca: None, tls_client: None, guest: None };
assert_eq!(refuse(&b).err().unwrap(), "[products.acme] no");
assert_eq!(no_identity(&b).err().unwrap(), "[products.acme] nothing to mint");
}
}