feat(cli): shared item_build module — secret resolution + type parsers

This commit is contained in:
adlee-was-taken
2026-06-20 17:21:43 -04:00
parent 517d52d517
commit 154b984725
2 changed files with 86 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
//! Shared per-type item construction + interactive editing for both the
//! personal vault (`commands/add.rs`, `commands/edit.rs`) and the org vault
//! (`commands/org.rs`). Centralizing it keeps the two surfaces from drifting.
use std::collections::HashMap;
use std::path::PathBuf;
use anyhow::{Context, Result};
use zeroize::Zeroizing;
use relicario_core::item::FieldHistoryEntry;
use relicario_core::item_types::{CardKind, TotpAlgorithm};
use relicario_core::{EncryptedAttachment, FieldId, Item, ItemCore};
pub(crate) type FieldHistory = HashMap<FieldId, Vec<FieldHistoryEntry>>;
/// Resolve a single-line secret: from stdin when `from_stdin`, else an
/// interactive masked prompt (which honours `RELICARIO_TEST_ITEM_SECRET`).
pub(crate) fn resolve_secret_line(from_stdin: bool, label: &str) -> Result<String> {
if from_stdin {
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
Ok(s.trim_end_matches(['\n', '\r']).to_string())
} else {
crate::prompt::prompt_secret(&format!("{label}: "))
}
}
/// Resolve a multiline secret (key material, note body). Both paths read stdin
/// to EOF; the interactive path first prints `hint` to stderr.
pub(crate) fn resolve_secret_multiline(from_stdin: bool, hint: &str) -> Result<String> {
if !from_stdin {
eprintln!("{hint}");
}
let mut s = String::new();
std::io::Read::read_to_string(&mut std::io::stdin(), &mut s)?;
Ok(s)
}
pub(crate) fn parse_card_kind(s: &str) -> Result<CardKind> {
Ok(match s {
"credit" => CardKind::Credit,
"debit" => CardKind::Debit,
"gift" => CardKind::Gift,
"loyalty" => CardKind::Loyalty,
"other" => CardKind::Other,
other => anyhow::bail!("unknown card kind: {other}"),
})
}
pub(crate) fn parse_totp_algorithm(s: &str) -> Result<TotpAlgorithm> {
Ok(match s.to_ascii_lowercase().as_str() {
"sha1" => TotpAlgorithm::Sha1,
"sha256" => TotpAlgorithm::Sha256,
"sha512" => TotpAlgorithm::Sha512,
other => anyhow::bail!("unknown algorithm: {other}"),
})
}
#[cfg(test)]
mod tests {
use super::*;
use relicario_core::item_types::{CardKind, TotpAlgorithm};
#[test]
fn card_kind_parses_known_values() {
assert_eq!(parse_card_kind("credit").unwrap(), CardKind::Credit);
assert_eq!(parse_card_kind("loyalty").unwrap(), CardKind::Loyalty);
}
#[test]
fn card_kind_rejects_unknown() {
assert!(parse_card_kind("platinum").is_err());
}
#[test]
fn totp_algorithm_is_case_insensitive() {
assert_eq!(parse_totp_algorithm("SHA256").unwrap(), TotpAlgorithm::Sha256);
}
#[test]
fn totp_algorithm_rejects_unknown() {
assert!(parse_totp_algorithm("md5").is_err());
}
}

View File

@@ -14,6 +14,7 @@ pub mod edit;
pub mod generate; pub mod generate;
pub mod get; pub mod get;
pub mod import; pub mod import;
pub mod item_build;
pub mod org; pub mod org;
pub mod init; pub mod init;
pub mod list; pub mod list;