use crate::{CliError, CliResult};
use serde::Deserialize;
use serde_norway::Value;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
const DEFAULT_PROFILE: &str = include_str!("../tools/message-profile.yaml");
#[derive(Debug, Deserialize, Default)]
pub struct Profile {
#[serde(default)]
pub transport: TransportProfile,
#[serde(default)]
pub message: MessageProfile,
#[serde(default)]
pub message_types: MessageTypesProfile,
#[serde(default)]
pub operator_interface: OperatorInterfaceProfile,
#[serde(default)]
pub audit: AuditProfile,
#[serde(default)]
pub shorthand: HashMap<String, ShorthandProfile>,
}
#[derive(Debug, Deserialize)]
pub struct TransportProfile {
#[serde(default = "default_transport")]
pub default: String,
}
impl Default for TransportProfile {
fn default() -> Self {
Self {
default: default_transport(),
}
}
}
#[derive(Debug, Deserialize)]
pub struct MessageProfile {
#[serde(default = "default_human_prefix_template")]
pub human_prefix_template: String,
#[serde(default = "default_structured_header_opener")]
pub structured_header_opener: String,
#[serde(default = "default_required_header_fields")]
pub required_header_fields: Vec<String>,
}
impl Default for MessageProfile {
fn default() -> Self {
Self {
human_prefix_template: default_human_prefix_template(),
structured_header_opener: default_structured_header_opener(),
required_header_fields: default_required_header_fields(),
}
}
}
#[derive(Debug, Deserialize, Default)]
pub struct MessageTypesProfile {
#[serde(default)]
pub per_type_required_fields: HashMap<String, Value>,
}
#[derive(Debug, Deserialize)]
pub struct OperatorInterfaceProfile {
#[serde(default = "default_workflow_status_enum")]
pub workflow_status_enum: Vec<String>,
}
impl Default for OperatorInterfaceProfile {
fn default() -> Self {
Self {
workflow_status_enum: default_workflow_status_enum(),
}
}
}
#[derive(Debug, Deserialize)]
pub struct AuditProfile {
#[serde(default = "default_redaction_policy_enum")]
pub redaction_policy_enum: Vec<String>,
#[serde(default = "default_delivery_status_enum")]
pub delivery_status_enum: Vec<String>,
#[serde(default = "default_force_hash_only_categories")]
pub force_hash_only_categories: Vec<String>,
}
impl Default for AuditProfile {
fn default() -> Self {
Self {
redaction_policy_enum: default_redaction_policy_enum(),
delivery_status_enum: default_delivery_status_enum(),
force_hash_only_categories: default_force_hash_only_categories(),
}
}
}
#[derive(Debug, Deserialize)]
pub struct ShorthandProfile {
#[serde(default)]
pub r#type: Option<String>,
#[serde(default)]
pub mode: Option<String>,
pub body_template: String,
}
fn default_transport() -> String {
"herdr".to_string()
}
fn default_human_prefix_template() -> String {
"[from-{agent_id} via {transport}]".to_string()
}
fn default_structured_header_opener() -> String {
"herdr".to_string()
}
fn default_required_header_fields() -> Vec<String> {
["from", "to", "mid", "type"]
.into_iter()
.map(str::to_string)
.collect()
}
fn default_workflow_status_enum() -> Vec<String> {
["idle", "working", "blocked", "waiting-for-operator", "done"]
.into_iter()
.map(str::to_string)
.collect()
}
fn default_redaction_policy_enum() -> Vec<String> {
["hash-only", "excerpt", "full"]
.into_iter()
.map(str::to_string)
.collect()
}
fn default_delivery_status_enum() -> Vec<String> {
["drafted", "sent", "observed", "failed", "unknown"]
.into_iter()
.map(str::to_string)
.collect()
}
fn default_force_hash_only_categories() -> Vec<String> {
[
"secrets",
"keys",
"tokens",
"private_credentials",
"sensitive_operator_text",
]
.into_iter()
.map(str::to_string)
.collect()
}
pub fn load_profile(path: Option<&Path>) -> CliResult<Profile> {
let content = match path {
Some(path) => fs::read_to_string(path).map_err(|error| {
CliError::failure(format!(
"failed to read profile {}: {error}",
path.display()
))
})?,
None => DEFAULT_PROFILE.to_string(),
};
serde_norway::from_str(&content)
.map_err(|error| CliError::failure(format!("profile must be valid YAML: {error}")))
}