merge(cycle-2): land Stream C — Plan B Phases 7+8 (core/wasm seam)
3 commits from feature/cli-tail-stream-c-core-wasm-seam: -e5d63abrefactor(core): extract base32 module, dedupe two RFC 4648 impls -03f2a1brefactor(core,cli): migrate CLI parsers to relicario-core, parse.rs becomes shim -fc9264efeat(wasm): add parse_month_year, base32_decode_lenient, guess_mime exports Phase 7 complete: parser bodies (MonthYear::parse, mime::guess_for_extension, TotpConfig::parse_secret) lifted into relicario-core; CLI parse.rs reduced to 19-line thin shims; callsites unchanged. base32 codec deduplicated from two inline implementations (item.rs encoder + import_lastpass.rs decoder) into crate::base32::{encode_rfc4648, decode_rfc4648_lenient}. Steam Guards non-RFC-4648 alphabet stays at item_types/totp.rs:13 with a neighbour comment cross-referencing the standard module. Phase 8 complete: 3 #[wasm_bindgen] exports (parse_month_year, base32_decode_lenient, guess_mime) with snake_case JS names per existing convention. extension/src/wasm.d.ts mirror landed in the same commit (fc9264e) per kickoff hard-rule. Spec deviation (PM ack 02:55Z + 15:13Z): pub(crate) mod base32 promoted to pub mod base32 because the CLI shim AND the Phase 8 WASM exports both require external reach. Justification documented in lib.rs module-list comment + module-level docstring on base32.rs explicitly carving Steam Guard out as a non-user. Two new RelicarioError variants added (additive, non-breaking): - InvalidBase32(String) - InvalidMonthYear(String) 3-way merge with stream-a (3dd1e1b) clean: stream-c didn't actually modify add.rs or prompt.rs, so the diff stat showing those files was just stream-c being behind on stream-a's changes. ort strategy auto-took mains versions. Pre-merge checklist on tipfc9264e+ post-merge verification: - cargo test --workspace standalone: 272 tests, 0 failures - cargo test --workspace post-merge: 277 tests, 0 failures (5 added from stream-a) - cargo clippy --workspace --all-targets: silent (both standalone + post-merge) - cargo build -p relicario-wasm --target wasm32-unknown-unknown: clean - Extension vitest: 17 failed / 335 passed -- matches cycle-1 baseline cluster, no new regressions - Independent fresh-subagent code review: APPROVE-WITH-NITS - nit 1: stale doc-comment in extension/src/shared/base32.ts:3 (Plan C concern, deferred) - nit 2: TotpConfig::parse_secret unused on this branch (spec-driven forward-compat for Plan C SW handlers) Plan B Phases 7+8 complete. With Phase 3 (Stream A) already merged at3dd1e1b, only Phases 4+5+6 (Stream B in flight) remain to close out Plan B. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,47 +1,19 @@
|
||||
//! Small parsers used by the CLI (`MM/YY[YY]`, lenient base32, MIME guess).
|
||||
//!
|
||||
//! Phase 7 of the CLI restructure migrates these to `relicario-core` and
|
||||
//! turns this file into a thin re-export shim. They live here for now so
|
||||
//! the Phase 1 relocation stays mechanical.
|
||||
//! Thin shims over `relicario-core`'s migrated parsers, kept here so existing
|
||||
//! CLI callsites need no import churn. Plan B Phase 7 moved the bodies into
|
||||
//! `relicario_core::{time::MonthYear::parse, base32::decode_rfc4648_lenient,
|
||||
//! mime::guess_for_extension}`.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::Result;
|
||||
use relicario_core::MonthYear;
|
||||
|
||||
pub(crate) fn parse_month_year(s: &str) -> Result<relicario_core::MonthYear> {
|
||||
// Accepts MM/YYYY or MM-YYYY or MM/YY.
|
||||
let (m_str, y_str) = s.split_once(['/', '-'])
|
||||
.ok_or_else(|| anyhow::anyhow!("expected MM/YYYY"))?;
|
||||
let month: u8 = m_str.parse().context("invalid month")?;
|
||||
let year: u16 = if y_str.len() == 2 {
|
||||
2000 + y_str.parse::<u16>().context("invalid 2-digit year")?
|
||||
} else {
|
||||
y_str.parse().context("invalid year")?
|
||||
};
|
||||
Ok(relicario_core::MonthYear { month, year })
|
||||
pub(crate) fn parse_month_year(s: &str) -> Result<MonthYear> {
|
||||
Ok(MonthYear::parse(s)?)
|
||||
}
|
||||
|
||||
pub(crate) fn guess_mime(filename: &str) -> String {
|
||||
let lower = filename.to_ascii_lowercase();
|
||||
match lower.rsplit_once('.').map(|(_, ext)| ext).unwrap_or("") {
|
||||
"pdf" => "application/pdf",
|
||||
"png" => "image/png",
|
||||
"jpg" | "jpeg" => "image/jpeg",
|
||||
"txt" => "text/plain",
|
||||
"json" => "application/json",
|
||||
_ => "application/octet-stream",
|
||||
}.to_string()
|
||||
relicario_core::mime::guess_for_extension(filename).to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn base32_decode_lenient(s: &str) -> Result<Vec<u8>> {
|
||||
let cleaned: String = s.chars()
|
||||
.filter(|c| !c.is_whitespace())
|
||||
.collect::<String>()
|
||||
.to_ascii_uppercase()
|
||||
.trim_end_matches('=')
|
||||
.to_string();
|
||||
let padded = {
|
||||
let rem = cleaned.len() % 8;
|
||||
if rem == 0 { cleaned } else { format!("{}{}", cleaned, "=".repeat(8 - rem)) }
|
||||
};
|
||||
data_encoding::BASE32.decode(padded.as_bytes())
|
||||
.map_err(|e| anyhow::anyhow!("invalid base32: {e}"))
|
||||
Ok(relicario_core::base32::decode_rfc4648_lenient(s)?)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user