1use std::path::{Path, PathBuf};
2
3use thiserror::Error;
4
5use crate::handle::{Channel, Fingerprint};
6
7pub fn display_path(p: &Path) -> String {
12 let s = p.to_string_lossy();
13 if s.starts_with("inline:") {
14 "<inline credential>".to_string()
15 } else {
16 s.into_owned()
17 }
18}
19
20#[derive(Debug, Error)]
21pub enum CredentialError {
22 #[error("account '{account}' not found in {channel} store")]
23 NotFound { channel: Channel, account: String },
24
25 #[error("agent '{agent}' not permitted on {channel}:{fp}")]
26 NotPermitted {
27 channel: Channel,
28 agent: String,
29 fp: Fingerprint,
30 },
31
32 #[error(
33 "credential file '{path}' has insecure permissions (mode {mode:o}); run `chmod 600 {path}`",
34 path = display_path(path)
35 )]
36 InsecurePermissions { path: PathBuf, mode: u32 },
37
38 #[error("credential file missing: {path}", path = display_path(path))]
39 FileMissing { path: PathBuf },
40
41 #[error("credential file unreadable ({path}): {source}", path = display_path(path))]
42 Unreadable {
43 path: PathBuf,
44 #[source]
45 source: std::io::Error,
46 },
47
48 #[error("google token expired and no refresh_token; run setup wizard for account '{account}'")]
49 GoogleExpired { account: String },
50
51 #[error("invalid secret file ({path}): {message}", path = display_path(path))]
52 InvalidSecret { path: PathBuf, message: String },
53
54 #[error(
55 "email account '{account}' references google_account_id='{google_account_id}' but no such google account exists"
56 )]
57 OrphanedGoogleRef {
58 account: String,
59 google_account_id: String,
60 },
61}
62
63#[derive(Debug, Error)]
67pub enum BuildError {
68 #[error(
69 "duplicate credential path: '{path}' used by both {a_channel}:{a_instance} and {b_channel}:{b_instance}",
70 path = display_path(path)
71 )]
72 DuplicatePath {
73 path: PathBuf,
74 a_channel: Channel,
75 a_instance: String,
76 b_channel: Channel,
77 b_instance: String,
78 },
79
80 #[error(
81 "overlapping session_dir: '{inner}' is a sub-path of '{outer}' — both would collide on Signal keys",
82 inner = display_path(inner), outer = display_path(outer)
83 )]
84 PathPrefixOverlap { outer: PathBuf, inner: PathBuf },
85
86 #[error("agent '{agent}' binds credentials.{channel}='{account}' but no such {channel} instance exists (available: {available:?})")]
87 MissingInstance {
88 channel: Channel,
89 agent: String,
90 account: String,
91 available: Vec<String>,
92 },
93
94 #[error("agent '{agent}' listens on multiple {channel} instances {instances:?} but did not declare credentials.{channel}; declare it explicitly")]
95 AmbiguousOutbound {
96 channel: Channel,
97 agent: String,
98 instances: Vec<String>,
99 },
100
101 #[error("{channel} instance '{instance}' allow_agents excludes '{agent}' but that agent declares credentials.{channel}='{instance}'")]
102 AllowAgentsExcludes {
103 channel: Channel,
104 instance: String,
105 agent: String,
106 },
107
108 #[error("agent '{agent}': credentials.{channel}='{outbound}' but inbound binding is '{inbound}' — asymmetric; silence with credentials.{channel}_asymmetric: true")]
109 AsymmetricBinding {
110 channel: Channel,
111 agent: String,
112 outbound: String,
113 inbound: String,
114 },
115
116 #[error("{channel} instance '{instance}': {source}")]
117 Credential {
118 channel: Channel,
119 instance: String,
120 #[source]
121 source: CredentialError,
122 },
123
124 #[error("agent '{agent}': inline google_auth is deprecated and not accepted under strict_credentials=true; migrate to config/plugins/google-auth.yaml")]
125 LegacyInlineGoogleAuth { agent: String },
126}
127
128#[derive(Debug, Error)]
129pub enum ResolveError {
130 #[error("agent '{agent}' has no credential bound for channel '{channel}'")]
131 Unbound { agent: String, channel: Channel },
132
133 #[error(transparent)]
134 Credential(#[from] CredentialError),
135}