pub mod bulk_estimated;
pub mod cli;
pub mod cmd;
pub mod config;
pub mod donation_details;
pub mod eventref;
pub mod input_form_store;
pub mod optimize_attest;
pub mod price_cache;
pub mod render;
pub mod resolve;
pub mod return_inputs;
pub mod session;
pub mod tax_profile;
pub mod testonly;
pub use cli::Cli;
pub use cmd::tranche::guard_allocation_vs_tranche;
pub use config::CliConfig;
pub use session::{
BulkFilter, BulkIncomeFilter, BulkIncomePlan, BulkIncomeRow, BulkLinkPlan, BulkLinkRow,
BulkReclassifyOutflowPlan, BulkReclassifyOutflowRow, BulkResolvePlan, BulkResolveRow,
BulkStiFilter, BulkStiPlan, BulkStiRow, BulkVoidPlan, BulkVoidRow, Frame, MatchAction,
MatchProposal, Session,
};
#[derive(Debug, thiserror::Error)]
pub enum CliError {
#[error(transparent)]
Store(#[from] btctax_store::StoreError),
#[error(transparent)]
Core(#[from] btctax_core::CoreError),
#[error(transparent)]
Adapter(#[from] btctax_adapters::AdapterError),
#[error("sqlite: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error("csv: {0}")]
Csv(#[from] csv::Error),
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("IRS form fill: {0}")]
FormFill(#[from] btctax_forms::FormsError),
#[error("not a valid event reference: {0:?}")]
BadEventRef(String),
#[error("usage: {0}")]
Usage(String),
#[error("unrecognized stored config value: key={key:?} value={value:?}")]
BadConfigValue { key: String, value: String },
#[error(
"the stored inputs for {year} predate the form-question registry (schema v{found}; this build reads \
v{expected}). Run `btctax income clear {year}` — which DISCARDS any carryover this row's prior \
reports computed onto it — then `btctax income import` for {year}; then, if this row carried a \
computed carryover, `btctax report --tax-year {prior} --write-carryover` to rebuild it.",
prior = year - 1
)]
StaleReturnInputs {
year: i32,
found: i64,
expected: i64,
},
#[error(
"year {year}'s parked full return is schema v{found} but this build expects v{expected}; \
an upgrade changed the input format. Its data lives only in the draft — do not discard it. \
Re-run on the app version that wrote it, or export it there first."
)]
StaleParkedDraft {
year: i32,
found: i64,
expected: i64,
},
#[error(
"year {year} holds a parked full return — in the form, 'use full return' to re-commit it, or \
'discard parked draft' (a confirmed delete) to drop it; then re-run this command."
)]
ParkedDraftBlocksWrite { year: i32 },
#[error(
"export refused: the ledger is pseudo-reconciled (a synthetic default contributes to the \
projection). To export this draft ON PURPOSE, attest the exact phrase {:?} (pass --attest, or \
type it at the prompt). Otherwise run `reconcile pseudo off` (or approve + attest the defaults).",
ATTEST_PHRASE
)]
AttestationRequired,
#[error(
"export refused: the attestation phrase did not match. The ledger is pseudo-reconciled; type the \
phrase EXACTLY (trimmed, case-sensitive): {:?}.",
ATTEST_PHRASE
)]
AttestationFailed,
#[error("io {path}: {source} ({hint})")]
PathIo {
path: String,
hint: String,
#[source]
source: std::io::Error,
},
}
pub const VAULT_OPEN_HINT: &str =
"check the --vault path, or run `btctax init` to create a new vault";
pub const EXPORT_OUT_HINT: &str =
"choose an --out path that does not already exist as a file and whose parent is writable";
pub fn store_io_with_path(
e: btctax_store::StoreError,
path: &std::path::Path,
hint: &str,
) -> CliError {
match e {
btctax_store::StoreError::Io(source) => CliError::PathIo {
path: path.display().to_string(),
hint: hint.to_string(),
source,
},
other => CliError::Store(other),
}
}
pub fn cli_io_with_path(e: CliError, path: &std::path::Path, hint: &str) -> CliError {
match e {
CliError::Io(source) | CliError::Store(btctax_store::StoreError::Io(source)) => {
CliError::PathIo {
path: path.display().to_string(),
hint: hint.to_string(),
source,
}
}
other => other,
}
}
pub const ATTEST_PHRASE: &str = "I attest this is true";
pub fn require_attestation(attest: Option<&str>) -> Result<(), CliError> {
match attest.map(str::trim) {
Some(p) if p == ATTEST_PHRASE => Ok(()),
Some(_) => Err(CliError::AttestationFailed),
None => Err(CliError::AttestationRequired),
}
}