ofdb_boundary/
lib.rs

1//#![deny(missing_docs)] // TODO: Complete missing documentation and enable this
2//#![deny(missing_docs)] option
3#![cfg_attr(features = "extra-derive", deny(missing_debug_implementations))]
4#![deny(rustdoc::broken_intra_doc_links)]
5#![cfg_attr(test, deny(warnings))]
6
7//! # ofdb-boundary
8//!
9//! Serializable, anemic data structures for accessing the OpenFairDB API in a
10//! type-safe manner.
11//!
12//! Only supposed to be used as short-lived, transitional instances for
13//! (de-)serializing entities!
14
15use serde::{Deserialize, Serialize};
16use time::Date;
17
18#[cfg(feature = "extra-derive")]
19use thiserror::Error;
20
21#[cfg(feature = "entity-conversions")]
22mod conv;
23
24type RevisionValue = u64;
25type Url = String;
26
27#[derive(Serialize, Deserialize)]
28#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, Copy, Eq, PartialEq))]
29pub struct UnixTimeMillis(i64);
30
31#[derive(Serialize, Deserialize)]
32#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, Copy, Eq, PartialEq))]
33pub struct UnixTimeSeconds(i64);
34
35impl From<time::OffsetDateTime> for UnixTimeSeconds {
36    fn from(from: time::OffsetDateTime) -> Self {
37        Self(from.unix_timestamp())
38    }
39}
40
41impl TryFrom<UnixTimeSeconds> for time::OffsetDateTime {
42    type Error = time::error::ComponentRange;
43    fn try_from(from: UnixTimeSeconds) -> Result<Self, Self::Error> {
44        Self::from_unix_timestamp(from.0)
45    }
46}
47
48#[rustfmt::skip]
49#[derive(Serialize, Deserialize)]
50#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq))]
51pub struct Entry {
52    pub id             : String,
53    pub created        : i64,
54    pub version        : u64,
55    pub title          : String,
56    pub description    : String,
57    pub lat            : f64,
58    pub lng            : f64,
59    pub street         : Option<String>,
60    pub zip            : Option<String>,
61    pub city           : Option<String>,
62    pub country        : Option<String>,
63    pub state          : Option<String>,
64    pub contact_name   : Option<String>,
65    pub email          : Option<String>,
66    pub telephone      : Option<String>,
67    pub homepage       : Option<Url>,
68    pub opening_hours  : Option<String>,
69    pub founded_on     : Option<Date>,
70    pub categories     : Vec<String>,
71    pub tags           : Vec<String>,
72    pub ratings        : Vec<String>,
73    pub license        : Option<String>,
74    pub image_url      : Option<Url>,
75    pub image_link_url : Option<Url>,
76
77    #[serde(rename = "custom", skip_serializing_if = "Vec::is_empty", default = "Default::default")]
78    pub custom_links   : Vec<CustomLink>,
79}
80
81#[rustfmt::skip]
82#[derive(Serialize, Deserialize)]
83#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq, Eq))]
84pub struct CustomLink {
85    pub url            : String,
86    pub title          : Option<String>,
87    pub description    : Option<String>,
88}
89
90#[rustfmt::skip]
91#[derive(Serialize, Deserialize)]
92#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq))]
93pub struct NewPlace {
94    pub title          : String,
95    pub description    : String,
96    pub lat            : f64,
97    pub lng            : f64,
98    pub street         : Option<String>,
99    pub zip            : Option<String>,
100    pub city           : Option<String>,
101    pub country        : Option<String>,
102    pub state          : Option<String>,
103    pub contact_name   : Option<String>,
104    pub email          : Option<String>,
105    pub telephone      : Option<String>,
106    pub homepage       : Option<String>,
107    pub opening_hours  : Option<String>,
108    pub founded_on     : Option<Date>,
109
110    #[serde(default = "Default::default")]
111    pub categories     : Vec<String>,
112
113    #[serde(default = "Default::default")]
114    pub tags           : Vec<String>,
115
116    #[serde(default = "Default::default")]
117    pub license        : String,
118
119    pub image_url      : Option<String>,
120    pub image_link_url : Option<String>,
121
122    #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")]
123    pub links          : Vec<CustomLink>,
124}
125
126#[rustfmt::skip]
127#[derive(Serialize, Deserialize)]
128#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq))]
129pub struct UpdatePlace {
130    pub version        : u64,
131    pub title          : String,
132    pub description    : String,
133    pub lat            : f64,
134    pub lng            : f64,
135    pub street         : Option<String>,
136    pub zip            : Option<String>,
137    pub city           : Option<String>,
138    pub country        : Option<String>,
139    pub state          : Option<String>,
140    pub contact_name   : Option<String>,
141    pub email          : Option<String>,
142    pub telephone      : Option<String>,
143    pub homepage       : Option<String>,
144    pub opening_hours  : Option<String>,
145    pub founded_on     : Option<Date>,
146    pub categories     : Vec<String>,
147    pub tags           : Vec<String>,
148    pub image_url      : Option<String>,
149    pub image_link_url : Option<String>,
150
151    #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")]
152    pub links          : Vec<CustomLink>,
153}
154
155#[derive(Serialize, Deserialize)]
156#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
157pub struct NewEvent {
158    pub title: String,
159    pub description: Option<String>,
160    pub start: i64,
161    pub end: Option<i64>,
162    pub lat: Option<f64>,
163    pub lng: Option<f64>,
164    pub street: Option<String>,
165    pub zip: Option<String>,
166    pub city: Option<String>,
167    pub country: Option<String>,
168    pub state: Option<String>,
169    pub email: Option<String>,
170    pub telephone: Option<String>,
171    pub homepage: Option<String>,
172    pub tags: Option<Vec<String>>,
173    pub created_by: Option<String>,
174    pub registration: Option<String>,
175    pub organizer: Option<String>,
176    pub image_url: Option<String>,
177    pub image_link_url: Option<String>,
178}
179
180#[derive(Serialize, Deserialize)]
181#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
182pub struct Event {
183    pub id: String,
184    pub title: String,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub description: Option<String>,
187    pub start: UnixTimeSeconds,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub end: Option<UnixTimeSeconds>,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub lat: Option<f64>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub lng: Option<f64>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub street: Option<String>,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub zip: Option<String>,
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub city: Option<String>,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub country: Option<String>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub state: Option<String>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub email: Option<String>,
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub telephone: Option<String>,
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub homepage: Option<String>,
210    pub tags: Vec<String>,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub registration: Option<String>,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub organizer: Option<String>,
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub image_url: Option<String>,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub image_link_url: Option<String>,
219}
220
221#[derive(Serialize, Deserialize)]
222#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, Copy, PartialEq))]
223pub struct Coordinate {
224    pub lat: f64,
225    pub lng: f64,
226}
227
228#[derive(Serialize, Deserialize)]
229#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
230pub struct NewUser {
231    pub email: String,
232    pub password: String,
233}
234
235#[derive(Serialize, Deserialize)]
236#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq))]
237pub struct User {
238    pub email: String,
239    pub email_confirmed: bool,
240    pub role: UserRole,
241}
242
243#[derive(Serialize, Deserialize)]
244#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, Copy))]
245pub struct RatingValue(i8);
246
247#[derive(Serialize, Deserialize)]
248#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, Copy))]
249pub struct AvgRatingValue(f64);
250
251impl From<f64> for AvgRatingValue {
252    fn from(v: f64) -> Self {
253        Self(v)
254    }
255}
256
257impl From<AvgRatingValue> for f64 {
258    fn from(from: AvgRatingValue) -> Self {
259        from.0
260    }
261}
262
263impl From<i8> for RatingValue {
264    fn from(from: i8) -> Self {
265        Self(from)
266    }
267}
268
269#[derive(Serialize, Deserialize)]
270#[cfg_attr(
271    feature = "extra-derive",
272    derive(Debug, Clone, Copy, PartialEq, Eq, Hash)
273)]
274#[serde(rename_all = "snake_case")]
275pub enum RatingContext {
276    Diversity,
277    Renewable,
278    Fairness,
279    Humanity,
280    Transparency,
281    Solidarity,
282}
283
284#[derive(Serialize, Deserialize)]
285#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
286pub struct EntrySearchRatings {
287    pub total: AvgRatingValue,
288    pub diversity: AvgRatingValue,
289    pub fairness: AvgRatingValue,
290    pub humanity: AvgRatingValue,
291    pub renewable: AvgRatingValue,
292    pub solidarity: AvgRatingValue,
293    pub transparency: AvgRatingValue,
294}
295
296#[derive(Serialize, Deserialize)]
297#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
298pub struct Comment {
299    pub id: String,
300    pub created: i64,
301    pub text: String,
302}
303
304#[derive(Serialize, Deserialize)]
305#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
306pub struct Category {
307    pub id: String,
308    pub name: String,
309}
310
311#[derive(Serialize, Deserialize)]
312#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
313pub struct PlaceSearchResult {
314    pub id: String,
315    pub status: Option<ReviewStatus>,
316    pub lat: f64,
317    pub lng: f64,
318    pub title: String,
319    pub description: String,
320    pub categories: Vec<String>,
321    pub tags: Vec<String>,
322    pub ratings: EntrySearchRatings,
323}
324
325#[derive(Serialize, Deserialize)]
326#[cfg_attr(
327    feature = "extra-derive",
328    derive(Debug, Clone, Copy, PartialEq, Eq, Hash)
329)]
330#[serde(rename_all = "lowercase")]
331pub enum ReviewStatus {
332    Archived,
333    Confirmed,
334    Created,
335    Rejected,
336}
337
338#[derive(Serialize, Deserialize)]
339#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
340pub struct Review {
341    pub status: ReviewStatus,
342    pub comment: Option<String>,
343}
344
345#[derive(Serialize, Deserialize)]
346#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
347pub struct SearchResponse {
348    pub visible: Vec<PlaceSearchResult>,
349    pub invisible: Vec<PlaceSearchResult>,
350}
351
352#[derive(Serialize, Deserialize)]
353#[cfg_attr(
354    feature = "extra-derive",
355    derive(Debug, Clone, Copy, PartialEq, Eq, Hash)
356)]
357#[serde(rename_all = "lowercase")]
358pub enum UserRole {
359    Guest,
360    User,
361    Scout,
362    Admin,
363}
364
365#[derive(Serialize, Deserialize)]
366#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq))]
367pub struct BboxSubscription {
368    pub id: String,
369    pub south_west_lat: f64,
370    pub south_west_lng: f64,
371    pub north_east_lat: f64,
372    pub north_east_lng: f64,
373}
374
375#[derive(Serialize, Deserialize)]
376#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
377pub struct MapBbox {
378    pub sw: MapPoint,
379    pub ne: MapPoint,
380}
381
382#[derive(Serialize, Deserialize)]
383#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
384pub struct MapPoint {
385    pub lat: f64,
386    pub lng: f64,
387}
388
389#[derive(Serialize, Deserialize)]
390#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
391pub struct RequestPasswordReset {
392    pub email: String,
393}
394
395#[derive(Serialize, Deserialize)]
396#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
397pub struct ResetPassword {
398    pub token: String,
399    pub new_password: String,
400}
401
402#[derive(Serialize, Deserialize)]
403#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, Default))]
404pub struct Credentials {
405    pub email: String,
406    pub password: String,
407}
408
409#[derive(Serialize, Deserialize)]
410#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
411pub struct ConfirmEmailAddress {
412    pub token: String,
413}
414
415#[derive(Serialize, Deserialize)]
416#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq, Eq))]
417pub struct TagFrequency(pub String, pub u64);
418
419#[derive(Serialize, Deserialize)]
420#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
421pub struct Rating {
422    pub id: String,
423    pub title: String,
424    pub created: i64,
425    pub value: RatingValue,
426    pub context: RatingContext,
427    pub comments: Vec<Comment>,
428    pub source: String,
429}
430
431#[derive(Serialize, Deserialize)]
432#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
433pub struct NewPlaceRating {
434    pub entry: String,
435    pub title: String,
436    pub value: RatingValue,
437    pub context: RatingContext,
438    pub comment: String,
439    pub source: Option<String>,
440    pub user: Option<String>,
441}
442
443#[derive(Serialize, Deserialize)]
444#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq, Eq))]
445pub struct PendingClearanceForPlace {
446    pub place_id: String,
447    pub created_at: UnixTimeMillis,
448    pub last_cleared_revision: Option<RevisionValue>,
449}
450
451#[derive(Serialize, Deserialize)]
452#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq, Eq))]
453pub struct ClearanceForPlace {
454    pub place_id: String,
455    pub cleared_revision: Option<RevisionValue>,
456}
457
458#[derive(Serialize, Deserialize)]
459#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq, Eq))]
460pub struct ResultCount {
461    pub count: u64,
462}
463
464#[derive(Serialize, Deserialize)]
465#[cfg_attr(feature = "extra-derive", derive(Debug, PartialEq))]
466pub struct LatLonDegrees(f64, f64);
467
468#[derive(Serialize, Deserialize, Default)]
469#[cfg_attr(feature = "extra-derive", derive(Debug, PartialEq, Eq))]
470pub struct Address {
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub street: Option<String>,
473
474    #[serde(skip_serializing_if = "Option::is_none")]
475    pub zip: Option<String>,
476
477    #[serde(skip_serializing_if = "Option::is_none")]
478    pub city: Option<String>,
479
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub country: Option<String>,
482
483    #[serde(skip_serializing_if = "Option::is_none")]
484    pub state: Option<String>,
485}
486
487impl Address {
488    pub fn is_empty(&self) -> bool {
489        self.street.is_none()
490            && self.zip.is_none()
491            && self.city.is_none()
492            && self.country.is_none()
493            && self.state.is_none()
494    }
495}
496
497#[derive(Serialize, Deserialize)]
498#[cfg_attr(feature = "extra-derive", derive(Debug, PartialEq))]
499pub struct Location {
500    #[serde(rename = "deg")]
501    pub latlon: LatLonDegrees,
502
503    #[serde(
504        rename = "adr",
505        skip_serializing_if = "Address::is_empty",
506        default = "Default::default"
507    )]
508    pub address: Address,
509}
510
511#[derive(Serialize, Deserialize, Default)]
512#[cfg_attr(feature = "extra-derive", derive(Debug, PartialEq, Eq))]
513pub struct Contact {
514    #[serde(skip_serializing_if = "Option::is_none")]
515    pub name: Option<String>,
516
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub email: Option<String>,
519
520    #[serde(skip_serializing_if = "Option::is_none")]
521    pub phone: Option<String>,
522}
523
524impl Contact {
525    pub fn is_empty(&self) -> bool {
526        self.email.is_none() && self.phone.is_none()
527    }
528}
529
530#[derive(Serialize, Deserialize, Default)]
531#[cfg_attr(feature = "extra-derive", derive(Debug, PartialEq, Eq))]
532pub struct Links {
533    #[serde(rename = "www", skip_serializing_if = "Option::is_none")]
534    pub homepage: Option<Url>,
535
536    #[serde(rename = "img", skip_serializing_if = "Option::is_none")]
537    pub image: Option<Url>,
538
539    #[serde(rename = "img_href", skip_serializing_if = "Option::is_none")]
540    pub image_href: Option<Url>,
541
542    #[serde(
543        rename = "custom",
544        skip_serializing_if = "Vec::is_empty",
545        default = "Default::default"
546    )]
547    pub custom: Vec<CustomLink>,
548}
549
550impl Links {
551    pub fn is_empty(&self) -> bool {
552        let Self {
553            homepage,
554            image,
555            image_href,
556            custom,
557        } = self;
558        homepage.is_none() && image.is_none() && image_href.is_none() && custom.is_empty()
559    }
560}
561
562#[derive(Serialize, Deserialize)]
563#[cfg_attr(feature = "extra-derive", derive(Debug, PartialEq, Eq))]
564pub struct Activity {
565    pub at: UnixTimeMillis,
566
567    #[serde(skip_serializing_if = "Option::is_none")]
568    pub by: Option<String>,
569}
570
571#[derive(Serialize, Deserialize)]
572#[cfg_attr(feature = "extra-derive", derive(Debug))]
573pub struct PlaceRoot {
574    pub id: String,
575
576    #[serde(rename = "lic")]
577    pub license: String,
578}
579
580#[derive(Serialize, Deserialize)]
581#[cfg_attr(feature = "extra-derive", derive(Debug))]
582pub struct PlaceRevision {
583    #[serde(rename = "rev")]
584    pub revision: u64,
585
586    pub created: Activity,
587
588    #[serde(rename = "tit")]
589    pub title: String,
590
591    #[serde(rename = "dsc")]
592    pub description: String,
593
594    #[serde(rename = "loc")]
595    pub location: Location,
596
597    #[serde(
598        rename = "cnt",
599        skip_serializing_if = "Contact::is_empty",
600        default = "Default::default"
601    )]
602    pub contact: Contact,
603
604    #[serde(rename = "hrs", skip_serializing_if = "Option::is_none")]
605    pub opening_hours: Option<String>,
606
607    #[serde(rename = "fnd", skip_serializing_if = "Option::is_none")]
608    pub founded_on: Option<Date>,
609
610    #[serde(
611        rename = "lnk",
612        skip_serializing_if = "Links::is_empty",
613        default = "Default::default"
614    )]
615    pub links: Links,
616
617    #[serde(
618        rename = "tag",
619        skip_serializing_if = "Vec::is_empty",
620        default = "Default::default"
621    )]
622    pub tags: Vec<String>,
623}
624
625#[derive(Serialize, Deserialize)]
626#[cfg_attr(feature = "extra-derive", derive(Debug))]
627pub struct PlaceHistory {
628    pub place: PlaceRoot,
629    pub revisions: Vec<(PlaceRevision, Vec<ReviewStatusLog>)>,
630}
631
632#[derive(Serialize, Deserialize)]
633#[cfg_attr(feature = "extra-derive", derive(Debug))]
634pub struct ActivityLog {
635    pub at: UnixTimeMillis,
636
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub by: Option<String>,
639
640    #[serde(skip_serializing_if = "Option::is_none")]
641    pub ctx: Option<String>,
642
643    #[serde(skip_serializing_if = "Option::is_none")]
644    pub comment: Option<String>,
645}
646
647#[derive(Serialize, Deserialize)]
648#[cfg_attr(feature = "extra-derive", derive(Debug))]
649pub struct ReviewStatusLog {
650    pub rev: u64,
651    pub act: ActivityLog,
652    pub status: ReviewStatus,
653}
654
655#[derive(Serialize, Deserialize)]
656#[cfg_attr(feature = "extra-derive", derive(Debug))]
657pub struct ReviewWithToken {
658    pub token: String,
659    pub status: ReviewStatus,
660}
661
662impl From<Entry> for UpdatePlace {
663    fn from(e: Entry) -> Self {
664        let Entry {
665            version,
666            title,
667            description,
668            lat,
669            lng,
670            street,
671            zip,
672            city,
673            country,
674            state,
675            contact_name,
676            email,
677            telephone,
678            homepage,
679            opening_hours,
680            founded_on,
681            categories,
682            tags,
683            image_url,
684            image_link_url,
685            custom_links,
686            ..
687        } = e;
688
689        UpdatePlace {
690            version,
691            title,
692            description,
693            lat,
694            lng,
695            street,
696            zip,
697            city,
698            country,
699            state,
700            contact_name,
701            email,
702            telephone,
703            homepage,
704            opening_hours,
705            founded_on,
706            categories,
707            tags,
708            image_url,
709            image_link_url,
710            links: custom_links,
711        }
712    }
713}
714
715#[derive(Serialize, Deserialize)]
716#[cfg_attr(feature = "extra-derive", derive(Debug, Clone))]
717pub struct JwtToken {
718    pub token: String,
719}
720
721#[derive(Serialize, Deserialize)]
722#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq, Eq, Error))]
723#[cfg_attr(feature = "extra-derive", error("{http_status}:{message}"))]
724pub struct Error {
725    /// HTTP status code
726    pub http_status: u16,
727    /// Error message
728    pub message: String,
729}
730
731#[derive(Serialize, Deserialize)]
732#[cfg_attr(feature = "extra-derive", derive(Debug, Clone, PartialEq, Eq))]
733pub enum DuplicateType {
734    SimilarChars,
735    SimilarWords,
736}