domain/document.rs
1//! Document intake, safety review, storage, and retention values.
2//!
3//! Documents carry vaccine proofs, waivers, medical records, incident evidence, and other source
4//! artifacts that staff and agents rely on. The domain separates received/extracted facts from
5//! verified facts, records virus/PII handling state, and keeps storage references explicit so
6//! automation cannot treat unreviewed uploads as compliance truth.
7
8use bon::Builder;
9use nutype::nutype;
10#[allow(unused_imports)]
11use serde::{Deserialize, Serialize};
12use std::fmt;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15/// Document classification used to route vaccine, waiver, medical, photo, and incident evidence.
16pub enum Classification {
17 /// Vaccine proof document classification or pipeline state used for review and retention.
18 VaccineProof,
19 /// Waiver document classification or pipeline state used for review and retention.
20 Waiver,
21 /// Photo document classification or pipeline state used for review and retention.
22 Photo,
23 /// Medical record document classification or pipeline state used for review and retention.
24 MedicalRecord,
25 /// Incident evidence document classification or pipeline state used for review and retention.
26 IncidentEvidence,
27 /// Non-dog, non-cat pet handled by exception policy.
28 Other,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32/// Source route through which a document entered the review and storage pipeline.
33pub enum Source {
34 /// Customer upload document classification or pipeline state used for review and retention.
35 CustomerUpload,
36 /// Staff scan document classification or pipeline state used for review and retention.
37 StaffScan,
38 /// Staff upload document classification or pipeline state used for review and retention.
39 StaffUpload,
40 /// Email ingest document classification or pipeline state used for review and retention.
41 EmailIngest,
42 /// Provider poll document classification or pipeline state used for review and retention.
43 ProviderPoll,
44 /// Provider webhook document classification or pipeline state used for review and retention.
45 ProviderWebhook,
46 /// Migration import document classification or pipeline state used for review and retention.
47 MigrationImport,
48 /// Provider role or status could not be mapped confidently.
49 Unknown,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53/// Normalized lifecycle states used to reconcile source-system data with domain workflows.
54pub enum Status {
55 /// Received document classification or pipeline state used for review and retention.
56 Received,
57 /// Quarantined rejected document classification or pipeline state used for review and retention.
58 QuarantinedRejected,
59 /// Extracting document classification or pipeline state used for review and retention.
60 Extracting,
61 /// Extraction failed document classification or pipeline state used for review and retention.
62 ExtractionFailed,
63 /// Awaiting review document classification or pipeline state used for review and retention.
64 AwaitingReview,
65 /// Verified document classification or pipeline state used for review and retention.
66 Verified,
67 /// Rejected document classification or pipeline state used for review and retention.
68 Rejected,
69 /// Superseded document classification or pipeline state used for review and retention.
70 Superseded,
71 /// Archived document classification or pipeline state used for review and retention.
72 Archived,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
76/// Virus-scan outcome used before documents may become staff-visible evidence.
77pub enum VirusScanStatus {
78 /// Pending document classification or pipeline state used for review and retention.
79 Pending,
80 /// Passed document classification or pipeline state used for review and retention.
81 Passed,
82 /// Deposit collection was attempted but did not succeed.
83 Failed,
84 /// Unsupported document classification or pipeline state used for review and retention.
85 Unsupported,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89/// PII redaction state used before document content is exposed to agents or reports.
90pub enum PiiRedactionStatus {
91 /// No deposit or review is needed for this reservation path.
92 NotRequired,
93 /// Pending document classification or pipeline state used for review and retention.
94 Pending,
95 /// Redacted document classification or pipeline state used for review and retention.
96 Redacted,
97 /// Deposit collection was attempted but did not succeed.
98 Failed,
99}
100
101#[nutype(
102 sanitize(trim),
103 validate(not_empty, len_char_max = 255),
104 derive(
105 Debug,
106 Clone,
107 PartialEq,
108 Eq,
109 PartialOrd,
110 Ord,
111 Hash,
112 Serialize,
113 Deserialize
114 )
115)]
116pub struct FileName(String);
117
118/// MIME type reported for a document before extraction, virus scanning, or storage policy decisions.
119#[nutype(
120 sanitize(trim),
121 validate(not_empty, len_char_max = 160),
122 derive(
123 Debug,
124 Clone,
125 PartialEq,
126 Eq,
127 PartialOrd,
128 Ord,
129 Hash,
130 Serialize,
131 Deserialize
132 )
133)]
134pub struct MimeType(String);
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
137/// Non-zero document size used to reject empty uploads before extraction or review.
138pub struct ContentLengthBytes(u64);
139
140impl ContentLengthBytes {
141 /// Promotes boundary input into a validated document domain value.
142 pub const fn try_new(value: u64) -> Result<Self, ContentLengthError> {
143 if value == 0 {
144 return Err(ContentLengthError::EmptyObject);
145 }
146 Ok(Self(value))
147 }
148
149 /// Exposes the validated scalar for serialization and adapter boundaries.
150 pub const fn get(self) -> u64 {
151 self.0
152 }
153}
154
155impl<'de> Deserialize<'de> for ContentLengthBytes {
156 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
157 where
158 D: serde::Deserializer<'de>,
159 {
160 Self::try_new(u64::deserialize(deserializer)?).map_err(serde::de::Error::custom)
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
165/// Validation error for document size constraints.
166pub enum ContentLengthError {
167 #[error("document storage evidence must not point at an empty object")]
168 /// Signals that object was blank or missing during document validation.
169 EmptyObject,
170}
171
172#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
173/// SHA-256 digest used to detect duplicate, tampered, or drifted document payloads.
174pub struct Sha256Digest(String);
175
176impl Sha256Digest {
177 /// Validates and creates the document value.
178 pub fn try_new(value: impl Into<String>) -> Result<Self, Sha256DigestError> {
179 let value = value.into().trim().to_ascii_lowercase();
180 if value.len() != 64 || !value.chars().all(|ch| ch.is_ascii_hexdigit()) {
181 return Err(Sha256DigestError::InvalidSha256Hex);
182 }
183 Ok(Self(value))
184 }
185
186 /// Returns the owned inner string for storage or outbound mapping.
187 pub fn into_inner(self) -> String {
188 self.0
189 }
190}
191
192impl fmt::Debug for Sha256Digest {
193 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
194 formatter.write_str("Sha256Digest(<redacted>)")
195 }
196}
197
198impl<'de> Deserialize<'de> for Sha256Digest {
199 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
200 where
201 D: serde::Deserializer<'de>,
202 {
203 Self::try_new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
208/// Validation error for document hash formatting.
209pub enum Sha256DigestError {
210 #[error("document content hashes must be 64 lowercase/uppercase hexadecimal sha256 characters")]
211 /// Signals that sha256 hex could not be parsed or accepted during document validation.
212 InvalidSha256Hex,
213}
214
215#[nutype(
216 sanitize(trim),
217 validate(not_empty, len_char_max = 160),
218 derive(
219 Debug,
220 Clone,
221 PartialEq,
222 Eq,
223 PartialOrd,
224 Ord,
225 Hash,
226 Serialize,
227 Deserialize
228 )
229)]
230pub struct StorageBucket(String);
231
232/// Storage key for the immutable document object used as review or compliance evidence.
233#[nutype(
234 sanitize(trim),
235 validate(not_empty, len_char_max = 500),
236 derive(
237 Debug,
238 Clone,
239 PartialEq,
240 Eq,
241 PartialOrd,
242 Ord,
243 Hash,
244 Serialize,
245 Deserialize
246 )
247)]
248pub struct StorageKey(String);
249
250/// Optional object-version marker for document retention and supersession audits.
251#[nutype(
252 sanitize(trim),
253 validate(not_empty, len_char_max = 160),
254 derive(
255 Debug,
256 Clone,
257 PartialEq,
258 Eq,
259 PartialOrd,
260 Ord,
261 Hash,
262 Serialize,
263 Deserialize
264 )
265)]
266pub struct StorageVersion(String);
267
268#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
269/// Storage pointer to the immutable object that backs a reviewed or source document.
270pub struct StorageRef {
271 /// Bucket fact promoted into this document contract.
272 pub bucket: StorageBucket,
273 /// Key fact promoted into this document contract.
274 pub key: StorageKey,
275 /// Version fact promoted into this document contract.
276 pub version: StorageVersion,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
280/// Original uploaded file metadata preserved for audit, extraction, and staff review.
281pub struct OriginalFile {
282 /// Filename fact promoted into this document contract.
283 pub filename: FileName,
284 /// Mime type fact promoted into this document contract.
285 pub mime_type: MimeType,
286 /// Content length fact promoted into this document contract.
287 pub content_length: ContentLengthBytes,
288 /// Sha 256 fact promoted into this document contract.
289 pub sha256: Sha256Digest,
290}