feat(cli): shared item_build module — secret resolution + type parsers
This commit is contained in:
85
crates/relicario-cli/src/commands/item_build.rs
Normal file
85
crates/relicario-cli/src/commands/item_build.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user