gingr/response.rs
1use crate::endpoint;
2use bytes::Bytes;
3use std::{collections::BTreeMap, fmt};
4
5#[derive(
6 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
7)]
8#[serde(transparent)]
9/// HTTP status wrapper used by Gingr transport and webhook acknowledgements.
10pub struct HttpStatus(u16);
11
12impl HttpStatus {
13 /// HTTP 200 acknowledgement used for accepted Gingr responses and webhooks.
14 pub const OK: Self = Self(200);
15 /// HTTP 403 status returned when Gingr rejects authorization or signature checks fail.
16 pub const FORBIDDEN: Self = Self(403);
17 /// HTTP 500 status used when downstream processing fails after a Gingr request.
18 pub const INTERNAL_SERVER_ERROR: Self = Self(500);
19
20 /// Wraps an already-observed Gingr identifier without claiming anything beyond provider provenance.
21 pub const fn new(value: u16) -> Self {
22 Self(value)
23 }
24
25 /// Returns the numeric HTTP status code.
26 pub const fn as_u16(self) -> u16 {
27 self.0
28 }
29
30 /// Classifies statuses that should trigger Gingr retry handling.
31 pub const fn is_gingr_retry_override_allowed(self) -> bool {
32 matches!(self.0, 100..=599) && self.0 != Self::OK.0 && self.0 != Self::FORBIDDEN.0
33 }
34}
35
36impl fmt::Display for HttpStatus {
37 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(formatter, "{}", self.0)
39 }
40}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
43/// Raw Gingr response body paired with status for later DTO decoding.
44pub struct Raw {
45 status: HttpStatus,
46 body: Bytes,
47}
48
49impl Raw {
50 /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
51 pub fn new(status: HttpStatus, body: impl Into<Bytes>) -> Self {
52 Self {
53 status,
54 body: body.into(),
55 }
56 }
57
58 /// Returns the HTTP status reported by Gingr.
59 pub fn status(&self) -> HttpStatus {
60 self.status
61 }
62
63 /// Returns the response body decoded from the Gingr transport.
64 pub fn body(&self) -> &[u8] {
65 &self.body
66 }
67}
68
69/// Provider response envelope boundary for Gingr provider payloads.
70pub mod provider {
71 use std::fmt;
72
73 #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
74 #[serde(transparent)]
75 /// Gingr error response DTO preserving provider diagnostics.
76 pub struct Error {
77 detail: String,
78 }
79
80 impl Error {
81 /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
82 pub fn new(detail: impl Into<String>) -> Self {
83 Self {
84 detail: detail.into(),
85 }
86 }
87
88 /// Returns the provider error detail string, if Gingr supplied one.
89 pub fn detail(&self) -> &str {
90 &self.detail
91 }
92 }
93
94 impl fmt::Display for Error {
95 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96 formatter.write_str(&self.detail)
97 }
98 }
99
100 #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
101 /// Provider-specific webhook payload body retained for downstream DTO mapping.
102 pub struct Payload(pub serde_json::Value);
103
104 impl fmt::Display for Payload {
105 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
106 formatter.write_str("<provider payload quarantined>")
107 }
108 }
109
110 #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
111 #[serde(transparent)]
112 /// Gingr owner email DTO used in response mapping.
113 pub struct Email(String);
114
115 impl Email {
116 /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
117 pub fn new(value: impl Into<String>) -> Self {
118 Self(value.into())
119 }
120
121 /// Returns the normalized provider or storage string slice.
122 pub fn as_str(&self) -> &str {
123 &self.0
124 }
125 }
126
127 impl fmt::Display for Email {
128 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
129 formatter.write_str(&self.0)
130 }
131 }
132}
133
134#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
135/// Raw Gingr webhook envelope before signature verification and required-field promotion.
136pub struct Envelope<T> {
137 /// Provider success flag from the Gingr envelope, retained as transport evidence rather than a domain outcome.
138 pub success: Option<bool>,
139 /// Provider error detail from the Gingr envelope, retained for diagnostics and retry decisions.
140 pub error: Option<provider::Error>,
141 /// Provider payload body carried inside the Gingr response envelope for later DTO decoding.
142 pub data: T,
143 #[serde(flatten)]
144 /// Extra provider fields preserved for audit and future mapping without becoming validated NVA facts.
145 pub unknown: serde_json::Map<String, serde_json::Value>,
146}
147
148#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
149/// Gingr owner response DTO before customer-domain mapping.
150pub struct OwnerRecord {
151 /// Provider record identifier observed in the Gingr payload.
152 pub id: endpoint::OwnerId,
153 #[serde(default)]
154 /// Owner first name observed in Gingr and used only as provider-sourced contact context.
155 pub first_name: Option<String>,
156 #[serde(default)]
157 /// Owner last name observed in Gingr and used only as provider-sourced contact context.
158 pub last_name: Option<String>,
159 #[serde(default)]
160 /// Email address observed from Gingr and carried as customer-contact evidence.
161 pub email: Option<provider::Email>,
162 #[serde(default, alias = "cell")]
163 /// Owner cell-phone value observed in Gingr and carried as contact evidence.
164 pub cell_phone: Option<String>,
165 #[serde(flatten)]
166 /// Extra provider fields preserved for audit and future mapping without becoming validated NVA facts.
167 pub unknown: BTreeMap<String, serde_json::Value>,
168}
169
170impl OwnerRecord {
171 /// Builds a readable owner name from first and last name fields.
172 pub fn display_name(&self) -> Option<String> {
173 let joined = [self.first_name.as_deref(), self.last_name.as_deref()]
174 .into_iter()
175 .flatten()
176 .map(str::trim)
177 .filter(|part| !part.is_empty())
178 .collect::<Vec<_>>()
179 .join(" ");
180 if joined.is_empty() {
181 None
182 } else {
183 Some(joined)
184 }
185 }
186}
187
188#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
189/// Gingr animal response DTO before pet-domain mapping.
190pub struct AnimalRecord {
191 /// Provider record identifier observed in the Gingr payload.
192 pub id: endpoint::AnimalId,
193 #[serde(default)]
194 /// Provider owner/customer identifier observed in the Gingr payload.
195 pub owner_id: Option<endpoint::OwnerId>,
196 #[serde(default)]
197 /// Provider display label retained for operator context; NVA-specific naming rules are applied downstream.
198 pub name: Option<String>,
199 #[serde(default)]
200 /// Provider species label for the animal; mapping code must validate any NVA pet-domain meaning separately.
201 pub species: Option<String>,
202 #[serde(default)]
203 /// Provider birthday string for the animal, retained raw because this crate does not validate age semantics.
204 pub birthday: Option<String>,
205 #[serde(flatten)]
206 /// Extra provider fields preserved for audit and future mapping without becoming validated NVA facts.
207 pub unknown: BTreeMap<String, serde_json::Value>,
208}
209
210#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
211/// Gingr reservation response DTO used to reconcile bookings and source evidence.
212pub struct ReservationRecord {
213 /// Provider record identifier observed in the Gingr payload.
214 pub id: endpoint::ReservationId,
215 #[serde(default)]
216 /// Provider owner/customer identifier observed in the Gingr payload.
217 pub owner_id: Option<endpoint::OwnerId>,
218 #[serde(default)]
219 /// Provider animal/pet identifier observed in the Gingr payload.
220 pub animal_id: Option<endpoint::AnimalId>,
221 #[serde(default)]
222 /// Provider status string preserved as source evidence until NVA validates a semantic status.
223 pub status: Option<String>,
224 #[serde(flatten)]
225 /// Extra provider fields preserved for audit and future mapping without becoming validated NVA facts.
226 pub unknown: BTreeMap<String, serde_json::Value>,
227}
228
229#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
230/// Gingr reference-data DTO for lookup tables such as locations or vets.
231pub struct ReferenceRecord {
232 /// Provider record identifier observed in the Gingr payload.
233 pub id: endpoint::ReferenceId,
234 #[serde(default)]
235 /// Provider display label retained for operator context; NVA-specific naming rules are applied downstream.
236 pub name: Option<String>,
237 #[serde(flatten)]
238 /// Extra provider fields preserved for audit and future mapping without becoming validated NVA facts.
239 pub unknown: BTreeMap<String, serde_json::Value>,
240}