Skip to main content

gingr/endpoint/
reservations.rs

1use super::{AnimalId, Date, DateRange, IsoDate, Limit, LocationId, Method, OwnerId, Request};
2
3/// Gingr reservation endpoint boundary with provider parameters kept explicit.
4pub mod reservation {
5    use super::*;
6
7    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
8    /// Provider reservation-type identifier used to classify boarding, daycare, grooming, training, or other Gingr service demand.
9    pub struct TypeId(u64);
10
11    impl TypeId {
12        /// Wraps an already-observed Gingr identifier without claiming anything beyond provider provenance.
13        pub const fn new(value: u64) -> Self {
14            Self(value)
15        }
16    }
17
18    impl core::fmt::Display for TypeId {
19        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
20            write!(formatter, "{}", self.0)
21        }
22    }
23
24    #[derive(Clone, Debug, Default, PartialEq, Eq)]
25    /// Request descriptor for Gingr reservation types, the provider lookup table behind service-line classification.
26    pub struct Types {
27        id: Option<TypeId>,
28        active_only: Option<bool>,
29    }
30
31    impl Types {
32        /// Starts a builder that makes each provider parameter explicit before request capture.
33        pub fn builder() -> TypesBuilder {
34            TypesBuilder::default()
35        }
36    }
37
38    #[derive(Clone, Debug, Default)]
39    /// Builder for reservation-type lookup parameters without asserting NVA service-line semantics.
40    pub struct TypesBuilder {
41        id: Option<TypeId>,
42        active_only: Option<bool>,
43    }
44
45    impl TypesBuilder {
46        /// Narrows the provider lookup to one Gingr identifier when supplied.
47        pub fn id(mut self, id: TypeId) -> Self {
48            self.id = Some(id);
49            self
50        }
51
52        /// Restricts reservation-type results to active provider records.
53        pub fn active_only(mut self, active_only: bool) -> Self {
54            self.active_only = Some(active_only);
55            self
56        }
57
58        /// Finalizes the provider request descriptor after required fields are present and wrappers have validated local invariants.
59        pub fn build(self) -> Types {
60            Types {
61                id: self.id,
62                active_only: self.active_only,
63            }
64        }
65    }
66
67    impl Request for Types {
68        fn method(&self) -> Method {
69            Method::Get
70        }
71
72        fn path(&self) -> &'static str {
73            "/api/v1/reservation_types"
74        }
75
76        fn parameters(&self) -> Vec<(String, String)> {
77            let mut params = Vec::new();
78            if let Some(id) = self.id {
79                params.push(("id".to_owned(), id.to_string()));
80            }
81            if let Some(active_only) = self.active_only {
82                params.push(("active_only".to_owned(), active_only.to_string()));
83            }
84            params
85        }
86    }
87
88    #[derive(Clone, Debug, PartialEq, Eq)]
89    /// Request descriptor for Gingr reservation widget data at a provider timestamp.
90    pub struct WidgetData {
91        timestamp: Date,
92    }
93
94    impl WidgetData {
95        /// Starts a builder that makes each provider parameter explicit before request capture.
96        pub fn builder() -> WidgetDataBuilder {
97            WidgetDataBuilder::default()
98        }
99    }
100
101    #[derive(Clone, Debug, Default)]
102    /// Builder for the reservation-widget timestamp filter.
103    pub struct WidgetDataBuilder {
104        timestamp: Option<Date>,
105    }
106
107    impl WidgetDataBuilder {
108        /// Sets the provider timestamp filter sent to the Gingr endpoint.
109        pub fn timestamp(mut self, timestamp: Date) -> Self {
110            self.timestamp = Some(timestamp);
111            self
112        }
113
114        /// Finalizes the provider request descriptor after required fields are present and wrappers have validated local invariants.
115        pub fn build(self) -> WidgetData {
116            WidgetData {
117                timestamp: self.timestamp.expect("WidgetData requires timestamp"),
118            }
119        }
120    }
121
122    impl Request for WidgetData {
123        fn method(&self) -> Method {
124            Method::Get
125        }
126
127        fn path(&self) -> &'static str {
128            "/api/v1/reservation_widget_data"
129        }
130
131        fn parameters(&self) -> Vec<(String, String)> {
132            vec![("timestamp".to_owned(), self.timestamp.to_string())]
133        }
134    }
135
136    #[derive(Clone, Debug, Default, PartialEq, Eq)]
137    /// Optional provider-side reservation filters shared by reservation lookup endpoints.
138    pub struct SearchFilters {
139        from_date: Option<IsoDate>,
140        to_date: Option<IsoDate>,
141        reservation_type_ids: Vec<TypeId>,
142        animal_ids: Vec<AnimalId>,
143        cancelled_only: Option<bool>,
144        confirmed_only: Option<bool>,
145        completed_only: Option<bool>,
146        limit: Option<Limit>,
147    }
148
149    impl SearchFilters {
150        /// Starts a builder that makes each provider parameter explicit before request capture.
151        pub fn builder() -> SearchFiltersBuilder {
152            SearchFiltersBuilder::default()
153        }
154
155        pub(super) fn parameters(&self) -> Vec<(String, String)> {
156            let mut params = Vec::new();
157            if let Some(from_date) = self.from_date {
158                params.push(("params[fromDate]".to_owned(), from_date.to_string()));
159            }
160            if let Some(to_date) = self.to_date {
161                params.push(("params[toDate]".to_owned(), to_date.to_string()));
162            }
163            for id in &self.reservation_type_ids {
164                params.push(("params[reservationTypeIds][]".to_owned(), id.to_string()));
165            }
166            for id in &self.animal_ids {
167                params.push(("params[animalIds][]".to_owned(), id.to_string()));
168            }
169            if let Some(value) = self.cancelled_only {
170                params.push(("params[cancelledOnly]".to_owned(), value.to_string()));
171            }
172            if let Some(value) = self.confirmed_only {
173                params.push(("params[confirmedOnly]".to_owned(), value.to_string()));
174            }
175            if let Some(value) = self.completed_only {
176                params.push(("params[completedOnly]".to_owned(), value.to_string()));
177            }
178            if let Some(limit) = self.limit {
179                params.push(("params[limit]".to_owned(), limit.to_string()));
180            }
181            params
182        }
183    }
184
185    #[derive(Clone, Debug, Default)]
186    /// Builder for provider reservation filters such as date, status flags, type IDs, and animal IDs.
187    pub struct SearchFiltersBuilder {
188        filters: SearchFilters,
189    }
190
191    impl SearchFiltersBuilder {
192        /// Sets the inclusive provider start date sent to Gingr.
193        pub fn from_date(mut self, date: IsoDate) -> Self {
194            self.filters.from_date = Some(date);
195            self
196        }
197
198        /// Sets the inclusive provider end date sent to Gingr.
199        pub fn to_date(mut self, date: IsoDate) -> Self {
200            self.filters.to_date = Some(date);
201            self
202        }
203
204        /// Adds a Gingr reservation-type identifier as a provider filter.
205        pub fn reservation_type_id(mut self, id: TypeId) -> Self {
206            self.filters.reservation_type_ids.push(id);
207            self
208        }
209
210        /// Adds a Gingr animal identifier as a provider filter.
211        pub fn animal_id(mut self, id: AnimalId) -> Self {
212            self.filters.animal_ids.push(id);
213            self
214        }
215
216        /// Requests only provider records Gingr marks as cancelled.
217        pub fn cancelled_only(mut self, value: bool) -> Self {
218            self.filters.cancelled_only = Some(value);
219            self
220        }
221
222        /// Requests only provider records Gingr marks as confirmed.
223        pub fn confirmed_only(mut self, value: bool) -> Self {
224            self.filters.confirmed_only = Some(value);
225            self
226        }
227
228        /// Requests only provider records Gingr marks as completed.
229        pub fn completed_only(mut self, value: bool) -> Self {
230            self.filters.completed_only = Some(value);
231            self
232        }
233
234        /// Sets the provider result limit so automation does not imply unbounded source coverage.
235        pub fn limit(mut self, limit: Limit) -> Self {
236            self.filters.limit = Some(limit);
237            self
238        }
239
240        /// Finalizes the provider request descriptor after required fields are present and wrappers have validated local invariants.
241        pub fn build(self) -> SearchFilters {
242            self.filters
243        }
244    }
245}
246
247#[derive(Clone, Debug, PartialEq, Eq)]
248/// Request descriptor for the `/api/v1/reservations` endpoint used as source evidence for occupancy and check-in workflows.
249pub struct Reservations {
250    checked_in: bool,
251    range: Option<DateRange>,
252    location: Option<LocationId>,
253}
254
255impl Reservations {
256    /// Starts a reservations request for currently checked-in stays.
257    pub fn checked_in() -> Builder {
258        Builder {
259            checked_in: true,
260            range: None,
261            location: None,
262        }
263    }
264
265    /// Starts a reservations request for an inclusive provider date range.
266    pub fn for_range(range: DateRange) -> Builder {
267        Builder {
268            checked_in: false,
269            range: Some(range),
270            location: None,
271        }
272    }
273}
274
275#[derive(Clone, Debug, PartialEq, Eq)]
276/// Builder for the primary reservations request, including checked-in/range mode and optional location scope.
277pub struct Builder {
278    checked_in: bool,
279    range: Option<DateRange>,
280    location: Option<LocationId>,
281}
282
283impl Builder {
284    /// Scopes the Gingr request to a provider location identifier.
285    pub fn location(mut self, location: LocationId) -> Self {
286        self.location = Some(location);
287        self
288    }
289
290    /// Finalizes the provider request descriptor after required fields are present and wrappers have validated local invariants.
291    pub fn build(self) -> Reservations {
292        Reservations {
293            checked_in: self.checked_in,
294            range: self.range,
295            location: self.location,
296        }
297    }
298}
299
300impl Request for Reservations {
301    fn method(&self) -> Method {
302        Method::Post
303    }
304
305    fn path(&self) -> &'static str {
306        "/api/v1/reservations"
307    }
308
309    fn parameters(&self) -> Vec<(String, String)> {
310        let mut params = vec![("checked_in".to_owned(), self.checked_in.to_string())];
311        if let Some(range) = self.range {
312            params.push(("start_date".to_owned(), range.start().to_string()));
313            params.push(("end_date".to_owned(), range.end().to_string()));
314        }
315        if let Some(location) = self.location {
316            params.push(("location_id".to_owned(), location.to_string()));
317        }
318        params
319    }
320}
321
322#[derive(Clone, Copy, Debug, PartialEq, Eq)]
323/// Typed Gingr/provider codes for restrict to values.
324pub enum RestrictTo {
325    /// Restricts reservation lookup to pending Gingr requests.
326    PendingRequests,
327    /// Restricts reservation lookup to currently checked-in reservations.
328    CurrentlyCheckedIn,
329    /// Restricts reservation lookup to future reservations.
330    Future,
331    /// Restricts reservation lookup to past reservations.
332    Past,
333    /// Restricts reservation lookup to wait-listed reservations.
334    WaitListed,
335}
336
337impl core::fmt::Display for RestrictTo {
338    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
339        let value = match self {
340            Self::PendingRequests => "pending_requests",
341            Self::CurrentlyCheckedIn => "currently_checked_in",
342            Self::Future => "future",
343            Self::Past => "past",
344            Self::WaitListed => "wait_listed",
345        };
346        formatter.write_str(value)
347    }
348}
349
350/// Reservation lookup endpoints keyed by related Gingr owner or animal records.
351pub mod by {
352    use super::*;
353
354    #[derive(Clone, Debug, PartialEq, Eq)]
355    /// Request descriptor for reservations related to one Gingr animal record.
356    pub struct Animal {
357        animal_id: AnimalId,
358        restrict_to: Option<RestrictTo>,
359        filters: Option<reservation::SearchFilters>,
360    }
361
362    impl Animal {
363        /// Notes that Gingr scopes this lookup to the API user's current location.
364        pub const LOCATION_SCOPE_CAVEAT: &'static str = "Reservation data for this endpoint is only pulled for the location the API user is currently logged into.";
365
366        /// Starts a builder that makes each provider parameter explicit before request capture.
367        pub fn builder() -> AnimalBuilder {
368            AnimalBuilder::default()
369        }
370    }
371
372    #[derive(Clone, Debug, Default)]
373    /// Builder for animal-keyed reservation lookups and their provider filters.
374    pub struct AnimalBuilder {
375        animal_id: Option<AnimalId>,
376        restrict_to: Option<RestrictTo>,
377        filters: Option<reservation::SearchFilters>,
378    }
379
380    impl AnimalBuilder {
381        /// Adds a Gingr animal identifier as a provider filter.
382        pub fn animal_id(mut self, id: AnimalId) -> Self {
383            self.animal_id = Some(id);
384            self
385        }
386
387        /// Applies Gingr reservation-scope tokens such as future, past, or wait-listed.
388        pub fn restrict_to(mut self, restrict_to: RestrictTo) -> Self {
389            self.restrict_to = Some(restrict_to);
390            self
391        }
392
393        /// Adds nested Gingr reservation search filters to the lookup request.
394        pub fn filter(mut self, filters: reservation::SearchFilters) -> Self {
395            self.filters = Some(filters);
396            self
397        }
398
399        /// Finalizes the provider request descriptor after required fields are present and wrappers have validated local invariants.
400        pub fn build(self) -> Animal {
401            Animal {
402                animal_id: self.animal_id.expect("Animal requires animal_id"),
403                restrict_to: self.restrict_to,
404                filters: self.filters,
405            }
406        }
407    }
408
409    impl Request for Animal {
410        fn method(&self) -> Method {
411            Method::Post
412        }
413
414        fn path(&self) -> &'static str {
415            "/api/v1/reservations_by_animal"
416        }
417
418        fn parameters(&self) -> Vec<(String, String)> {
419            let mut params = vec![("id".to_owned(), self.animal_id.to_string())];
420            if let Some(restrict_to) = self.restrict_to {
421                params.push(("restrict_to".to_owned(), restrict_to.to_string()));
422            }
423            if let Some(filters) = &self.filters {
424                params.extend(filters.parameters());
425            }
426            params
427        }
428    }
429
430    #[derive(Clone, Debug, PartialEq, Eq)]
431    /// Request descriptor for reservations related to one Gingr owner/customer record.
432    pub struct Owner {
433        owner_id: OwnerId,
434        restrict_to: Option<RestrictTo>,
435        filters: Option<reservation::SearchFilters>,
436    }
437
438    impl Owner {
439        /// Notes that Gingr scopes this lookup to the API user's current location.
440        pub const LOCATION_SCOPE_CAVEAT: &'static str = Animal::LOCATION_SCOPE_CAVEAT;
441
442        /// Starts a builder that makes each provider parameter explicit before request capture.
443        pub fn builder() -> OwnerBuilder {
444            OwnerBuilder::default()
445        }
446    }
447
448    #[derive(Clone, Debug, Default)]
449    /// Builder for owner-keyed reservation lookups and their provider filters.
450    pub struct OwnerBuilder {
451        owner_id: Option<OwnerId>,
452        restrict_to: Option<RestrictTo>,
453        filters: Option<reservation::SearchFilters>,
454    }
455
456    impl OwnerBuilder {
457        /// Narrows the provider request to one Gingr owner/customer identifier.
458        pub fn owner_id(mut self, id: OwnerId) -> Self {
459            self.owner_id = Some(id);
460            self
461        }
462
463        /// Applies Gingr reservation-scope tokens such as future, past, or wait-listed.
464        pub fn restrict_to(mut self, restrict_to: RestrictTo) -> Self {
465            self.restrict_to = Some(restrict_to);
466            self
467        }
468
469        /// Adds nested Gingr reservation search filters to the lookup request.
470        pub fn filter(mut self, filters: reservation::SearchFilters) -> Self {
471            self.filters = Some(filters);
472            self
473        }
474
475        /// Finalizes the provider request descriptor after required fields are present and wrappers have validated local invariants.
476        pub fn build(self) -> Owner {
477            Owner {
478                owner_id: self.owner_id.expect("Owner requires owner_id"),
479                restrict_to: self.restrict_to,
480                filters: self.filters,
481            }
482        }
483    }
484
485    impl Request for Owner {
486        fn method(&self) -> Method {
487            Method::Post
488        }
489
490        fn path(&self) -> &'static str {
491            "/api/v1/reservations_by_owner"
492        }
493
494        fn parameters(&self) -> Vec<(String, String)> {
495            let mut params = vec![("id".to_owned(), self.owner_id.to_string())];
496            if let Some(restrict_to) = self.restrict_to {
497                params.push(("restrict_to".to_owned(), restrict_to.to_string()));
498            }
499            if let Some(filters) = &self.filters {
500                params.extend(filters.parameters());
501            }
502            params
503        }
504    }
505}
506
507#[derive(Clone, Copy, Debug, PartialEq, Eq)]
508/// Positive future-minute window used by Gingr back-of-house operational views.
509pub struct MinutesFuture(u64);
510
511impl MinutesFuture {
512    /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
513    pub fn new(value: u64) -> super::Result<Self> {
514        if value == 0 {
515            return Err(super::Error::InvalidPositiveInteger { value });
516        }
517        Ok(Self(value))
518    }
519}
520
521impl core::fmt::Display for MinutesFuture {
522    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
523        write!(formatter, "{}", self.0)
524    }
525}
526
527#[derive(Clone, Debug, PartialEq, Eq)]
528/// Request descriptor for Gingr back-of-house views used as raw operational evidence for near-term labor planning.
529pub struct BackOfHouse {
530    location: LocationId,
531    reservation_type_ids: Vec<reservation::TypeId>,
532    minutes_future: Option<MinutesFuture>,
533    full_day: Option<bool>,
534}
535
536impl BackOfHouse {
537    /// Starts a builder that makes each provider parameter explicit before request capture.
538    pub fn builder() -> BackOfHouseBuilder {
539        BackOfHouseBuilder::default()
540    }
541}
542
543#[derive(Clone, Debug, Default)]
544/// Builder for back-of-house location, reservation-type, time-window, and full-day provider filters.
545pub struct BackOfHouseBuilder {
546    location: Option<LocationId>,
547    reservation_type_ids: Vec<reservation::TypeId>,
548    minutes_future: Option<MinutesFuture>,
549    full_day: Option<bool>,
550}
551
552impl BackOfHouseBuilder {
553    /// Scopes the Gingr request to a provider location identifier.
554    pub fn location(mut self, location: LocationId) -> Self {
555        self.location = Some(location);
556        self
557    }
558
559    /// Adds a Gingr reservation-type identifier as a provider filter.
560    pub fn reservation_type_id(mut self, id: reservation::TypeId) -> Self {
561        self.reservation_type_ids.push(id);
562        self
563    }
564
565    /// Limits back-of-house results to a future provider time window.
566    pub fn minutes_future(mut self, minutes: MinutesFuture) -> Self {
567        self.minutes_future = Some(minutes);
568        self
569    }
570
571    /// Requests Gingr full-day back-of-house records when the provider supports that flag.
572    pub fn full_day(mut self, full_day: bool) -> Self {
573        self.full_day = Some(full_day);
574        self
575    }
576
577    /// Finalizes the provider request descriptor after required fields are present and wrappers have validated local invariants.
578    pub fn build(self) -> BackOfHouse {
579        BackOfHouse {
580            location: self.location.expect("BackOfHouse requires location"),
581            reservation_type_ids: self.reservation_type_ids,
582            minutes_future: self.minutes_future,
583            full_day: self.full_day,
584        }
585    }
586}
587
588impl Request for BackOfHouse {
589    fn method(&self) -> Method {
590        Method::Get
591    }
592
593    fn path(&self) -> &'static str {
594        "/api/v1/back_of_house"
595    }
596
597    fn parameters(&self) -> Vec<(String, String)> {
598        let mut params = vec![("location_id".to_owned(), self.location.to_string())];
599        for id in &self.reservation_type_ids {
600            params.push(("type_ids[]".to_owned(), id.to_string()));
601        }
602        if let Some(minutes) = self.minutes_future {
603            params.push(("mins_future".to_owned(), minutes.to_string()));
604        }
605        if let Some(full_day) = self.full_day {
606            params.push(("full_day".to_owned(), full_day.to_string()));
607        }
608        params
609    }
610}
611
612#[derive(Clone, Debug, PartialEq, Eq)]
613/// Request descriptor for Gingr service discovery by reservation type; DTO mapping is intentionally not guaranteed here.
614pub struct GetServicesByType {
615    type_id: reservation::TypeId,
616    location: Option<LocationId>,
617}
618
619impl GetServicesByType {
620    /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
621    pub fn new(type_id: reservation::TypeId) -> Self {
622        Self {
623            type_id,
624            location: None,
625        }
626    }
627
628    /// Scopes the Gingr request to a provider location identifier.
629    pub fn location(mut self, location: LocationId) -> Self {
630        self.location = Some(location);
631        self
632    }
633}
634
635impl Request for GetServicesByType {
636    fn method(&self) -> Method {
637        Method::Get
638    }
639
640    fn path(&self) -> &'static str {
641        "/api/v1/get_services_by_type"
642    }
643
644    fn parameters(&self) -> Vec<(String, String)> {
645        let mut params = vec![("type_id".to_owned(), self.type_id.to_string())];
646        if let Some(location) = self.location {
647            params.push(("location_id".to_owned(), location.to_string()));
648        }
649        params
650    }
651}