1use crate::digest::DigestSha2_256;
2#[cfg(feature = "sqlx-postgres")]
3use crate::pg_num::PgU8;
4use crate::user::{ShortUser, UserIdRef};
5use async_trait::async_trait;
6use chrono::{DateTime, TimeZone, Timelike, Utc};
7use core::any::type_name;
8use core::fmt;
9use core::ops;
10#[cfg(feature = "sqlx-postgres")]
11use sqlx::postgres::types::PgRange;
12#[cfg(feature = "sqlx-postgres")]
13use sqlx::{postgres, Postgres};
14use std::cmp::Ordering;
15use std::ops::{Range, RangeFrom};
16use thiserror::Error;
17use uuid::Uuid;
18
19declare_new_uuid! {
20 pub struct IdempotencyKey(Uuid);
28 pub type ParseError = IdempotencyKeyParseError;
29 const SQL_NAME = "idempotency_key";
30}
31
32pub struct Cursor {
33 pub time: Instant,
35 pub fingerprint: DigestSha2_256,
37 pub position: Uuid,
39 pub dir: CursorDir,
43}
44
45pub enum CursorDir {
46 Ascending,
47 Descending,
48}
49
50pub type HtmlFragment = String;
51
52#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub struct PrUrl {
60 full: url::Url,
61 auto_scheme: bool,
63}
64
65impl fmt::Debug for PrUrl {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 f.debug_struct(type_name::<Self>())
68 .field("full", &self.full.as_str())
69 .field("auto_scheme", &self.auto_scheme)
70 .finish()
71 }
72}
73
74impl PrUrl {
75 pub fn as_url(&self) -> &url::Url {
76 &self.full
77 }
78
79 pub fn as_prurl(&self) -> &str {
80 let url_str = self.full.as_str();
81 let scheme = self.full.scheme();
82 let after_scheme = &url_str[scheme.len()..];
83 if after_scheme.starts_with("://") {
84 &after_scheme[1..]
85 } else {
86 after_scheme.strip_prefix(':').unwrap_or(after_scheme)
87 }
88 }
89
90 pub fn parse(input: &str) -> Result<Self, url::ParseError> {
91 if input.starts_with("//") {
92 url::Url::parse(&format!("https:{input}")).map(|full| Self {
93 full,
94 auto_scheme: true,
95 })
96 } else {
97 url::Url::parse(input).map(|full| Self {
98 full,
99 auto_scheme: false,
100 })
101 }
102 }
103}
104
105#[cfg(feature = "serde")]
106impl serde::Serialize for PrUrl {
107 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108 where
109 S: serde::Serializer,
110 {
111 self.full.serialize(serializer)
112 }
113}
114
115#[cfg(feature = "serde")]
116impl<'de> serde::Deserialize<'de> for PrUrl {
117 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
118 where
119 D: serde::Deserializer<'de>,
120 {
121 use serde::de::Error;
122 let input = std::borrow::Cow::<'de, str>::deserialize(deserializer)?;
123 Self::parse(input.as_ref()).map_err(|e| D::Error::custom(format!("{}", crate::types::DisplayErrorChain(&e))))
124 }
125}
126
127declare_new_enum!(
128 pub enum LocaleId {
129 #[str("de-DE")]
130 DeDe,
131 #[str("en-US")]
132 EnUs,
133 #[str("eo")]
134 Eo,
135 #[str("es-SP")]
136 EsSp,
137 #[str("fr-FR")]
138 FrFr,
139 #[str("it-IT")]
140 ItIt,
141 #[str("pt-PT")]
142 PtPt,
143 }
144 pub type ParseError = LocaleIdParseError;
145 const SQL_NAME = "locale_id";
146);
147
148#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
150pub struct Date(chrono::NaiveDate);
151
152#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Error)]
153#[error("invalid date ymd components: year = {year}, month = {month}, day = {day}")]
154pub struct InvalidDateYmd {
155 pub year: i32,
156 pub month: u32,
157 pub day: u32,
158}
159
160impl Date {
161 pub fn into_chrono(self) -> chrono::NaiveDate {
162 self.0
163 }
164
165 pub fn ymd(year: i32, month: u32, day: u32) -> Result<Self, InvalidDateYmd> {
176 chrono::NaiveDate::from_ymd_opt(year, month, day)
177 .map(Self)
178 .ok_or(InvalidDateYmd { year, month, day })
179 }
180}
181
182impl fmt::Display for Date {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 const DATE_FORMAT: &str = "%F";
185 write!(f, "{}", self.0.format(DATE_FORMAT))
186 }
187}
188
189#[cfg(feature = "serde")]
190impl serde::Serialize for Date {
191 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
192 where
193 S: serde::Serializer,
194 {
195 self.to_string().serialize(serializer)
196 }
197}
198
199#[cfg(feature = "serde")]
200impl<'de> serde::Deserialize<'de> for Date {
201 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
202 where
203 D: serde::Deserializer<'de>,
204 {
205 let inner = chrono::NaiveDate::deserialize(deserializer)?;
206 Ok(Self(inner))
207 }
208}
209
210#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
212pub struct Instant(DateTime<Utc>);
213
214const NANOSECOND_PER_MILLISECOND: u32 = 1_000_000;
215const MILLISECOND_PER_SECOND: u32 = 1_000;
216
217impl Instant {
218 pub fn new_round_down(inner: DateTime<Utc>) -> Self {
220 let nanos = inner.nanosecond();
221 Self(
222 inner
223 .with_nanosecond(nanos - (nanos % NANOSECOND_PER_MILLISECOND))
224 .expect("invalid time"),
225 )
226 }
227
228 pub fn ymd_hms(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> Self {
231 #[allow(deprecated)]
232 Self(Utc.ymd(year, month, day).and_hms(hour, min, sec))
233 }
234
235 pub fn ymd_hms_milli(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, milli: u32) -> Self {
238 #[allow(deprecated)]
239 Self(Utc.ymd(year, month, day).and_hms_milli(hour, min, sec, milli))
240 }
241
242 pub fn from_posix_timestamp(secs: i64) -> Self {
248 Self(Utc.timestamp_opt(secs, 0).single().expect("invalid timestamp"))
249 }
250
251 pub fn into_chrono(self) -> DateTime<Utc> {
252 self.0
253 }
254
255 pub const fn from_chrono(t: DateTime<Utc>) -> Self {
256 Self(t)
257 }
258
259 pub fn into_std(self) -> ::std::time::Instant {
261 let chrono_now = Utc::now();
262 let chrono_duration = self.0.signed_duration_since(chrono_now);
263 let (is_neg, std_duration) = if chrono_duration < ::chrono::Duration::zero() {
264 (true, (-chrono_duration).to_std().expect("duration out of range"))
265 } else {
266 (false, chrono_duration.to_std().expect("duration out of range"))
267 };
268 let std_now = ::std::time::Instant::now();
269 if is_neg {
270 std_now - std_duration
271 } else {
272 std_now + std_duration
273 }
274 }
275
276 pub fn into_system(self) -> ::std::time::SystemTime {
278 let (secs, nanos) = (self.0.timestamp(), self.0.timestamp_subsec_nanos());
279 let (is_neg, secs) = if secs < 0 {
280 (true, secs.checked_neg().expect("impossible to turn `secs` positive"))
281 } else {
282 (false, secs)
283 };
284 debug_assert!(secs >= 0);
285 let secs = u64::try_from(secs).expect("failed to convert positive i64 to u64");
286 let std_since_epoch = std::time::Duration::new(secs, nanos);
287 if is_neg {
288 std::time::SystemTime::UNIX_EPOCH - std_since_epoch
289 } else {
290 std::time::SystemTime::UNIX_EPOCH + std_since_epoch
291 }
292 }
293
294 pub fn from_system(t: ::std::time::SystemTime) -> Self {
295 Self::from_chrono(DateTime::<Utc>::from(t))
296 }
297
298 pub const fn into_posix_timestamp(self) -> i64 {
299 self.0.timestamp()
300 }
301
302 pub fn duration_since(&self, earlier: Instant) -> Duration {
303 Duration(self.0.signed_duration_since(earlier.0))
304 }
305
306 pub const fn const_cmp(self, other: Self) -> Ordering {
307 let left: i64 = self.into_posix_timestamp();
308 let right: i64 = other.into_posix_timestamp();
309 if left == right {
310 Ordering::Equal
311 } else if left < right {
312 Ordering::Less
313 } else {
314 Ordering::Greater
315 }
316 }
317}
318
319impl fmt::Display for Instant {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 fmt::Display::fmt(&self.0, f)
322 }
323}
324
325#[cfg(feature = "serde")]
326impl serde::Serialize for Instant {
327 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
328 where
329 S: serde::Serializer,
330 {
331 const INSTANT_FORMAT: &str = "%FT%T%.3fZ";
332
333 self.0.format(INSTANT_FORMAT).to_string().serialize(serializer)
334 }
335}
336
337#[cfg(feature = "serde")]
338impl<'de> serde::Deserialize<'de> for Instant {
339 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
340 where
341 D: serde::Deserializer<'de>,
342 {
343 let inner = DateTime::<Utc>::deserialize(deserializer)?;
344 Ok(Self::new_round_down(inner))
345 }
346}
347
348#[cfg(feature = "sqlx-postgres")]
349impl sqlx::Type<Postgres> for Instant {
350 fn type_info() -> postgres::PgTypeInfo {
351 postgres::PgTypeInfo::with_name("instant")
352 }
353
354 fn compatible(ty: &postgres::PgTypeInfo) -> bool {
355 *ty == <Self as ::sqlx::Type<Postgres>>::type_info()
356 || *ty == <DateTime<Utc> as ::sqlx::Type<Postgres>>::type_info()
357 }
358}
359
360#[cfg(feature = "sqlx-sqlite")]
361impl ::sqlx::Type<sqlx::Sqlite> for Instant {
362 fn type_info() -> ::sqlx::sqlite::SqliteTypeInfo {
363 <DateTime<Utc> as ::sqlx::Type<::sqlx::Sqlite>>::type_info()
364 }
365
366 fn compatible(ty: &::sqlx::sqlite::SqliteTypeInfo) -> bool {
367 <DateTime<Utc> as ::sqlx::Type<::sqlx::Sqlite>>::compatible(ty)
368 }
369}
370
371#[cfg(feature = "sqlx")]
372impl<'r, Db: ::sqlx::Database> ::sqlx::Decode<'r, Db> for Instant
373where
374 DateTime<Utc>: ::sqlx::Decode<'r, Db>,
375{
376 fn decode(value: <Db as ::sqlx::database::HasValueRef<'r>>::ValueRef) -> Result<Self, sqlx::error::BoxDynError> {
377 let value: DateTime<Utc> = <DateTime<Utc> as ::sqlx::Decode<Db>>::decode(value)?;
378 Ok(Self::new_round_down(value))
379 }
380}
381
382#[cfg(feature = "sqlx-postgres")]
383impl sqlx::Encode<'_, Postgres> for Instant {
384 fn encode_by_ref(&self, buf: &mut postgres::PgArgumentBuffer) -> sqlx::encode::IsNull {
385 let v: DateTime<Utc> = self.into_chrono();
386 <DateTime<Utc> as sqlx::Encode<'_, Postgres>>::encode(v, buf)
387 }
388}
389
390#[cfg(feature = "sqlx-sqlite")]
391impl ::sqlx::Encode<'_, ::sqlx::Sqlite> for Instant {
392 fn encode_by_ref(&self, args: &mut Vec<::sqlx::sqlite::SqliteArgumentValue<'_>>) -> ::sqlx::encode::IsNull {
393 <DateTime<Utc> as sqlx::Encode<'_, ::sqlx::Sqlite>>::encode(self.0, args)
394 }
395}
396
397impl ops::Add<Duration> for Instant {
398 type Output = Instant;
399
400 fn add(self, rhs: Duration) -> Self::Output {
401 Self::new_round_down(self.into_chrono() + rhs.0)
402 }
403}
404
405impl ops::Sub<Duration> for Instant {
406 type Output = Instant;
407
408 fn sub(self, rhs: Duration) -> Self::Output {
409 Self::new_round_down(self.into_chrono() - rhs.0)
410 }
411}
412
413impl ops::Sub<Instant> for Instant {
414 type Output = Duration;
415
416 fn sub(self, rhs: Instant) -> Self::Output {
417 Duration::from_chrono(self.into_chrono().signed_duration_since(rhs.into_chrono()))
418 }
419}
420
421#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
423pub struct Duration(::chrono::Duration);
424
425impl Duration {
426 #[inline(always)]
427 pub const fn into_std(self) -> std::time::Duration {
428 match self.0.to_std() {
429 Ok(d) => d,
430 Err(_) => panic!("invalid `Durattion` value"),
431 }
432 }
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
436#[error("standard Duration does not fit in Eternaltwin duration range: {0:?}")]
437pub struct StdDurationRangeError(pub std::time::Duration);
438
439impl Duration {
440 pub const ZERO: Self = Self(::chrono::Duration::zero());
441
442 pub fn from_std(d: std::time::Duration) -> Result<Self, StdDurationRangeError> {
443 ::chrono::Duration::from_std(d)
444 .map(Self)
445 .map_err(|_| StdDurationRangeError(d))
446 }
447
448 pub const fn from_chrono(d: ::chrono::Duration) -> Self {
449 Self(d)
450 }
451
452 pub const fn from_days(days: i64) -> Self {
453 Self::from_seconds(days * 86400)
454 }
455
456 pub const fn from_minutes(minutes: i64) -> Self {
457 Self::from_seconds(minutes * 60)
458 }
459
460 pub const fn from_seconds(seconds: i64) -> Self {
461 Self::from_millis(seconds * 1000)
462 }
463
464 pub const fn from_hours(hours: i64) -> Self {
465 Self::from_seconds(hours * 3600)
466 }
467
468 pub const fn from_millis(millis: i64) -> Self {
469 Self(match ::chrono::Duration::try_milliseconds(millis) {
470 Some(d) => d,
471 None => panic!("Duration::from_millis out of bounds"),
472 })
473 }
474
475 pub const fn seconds(&self) -> i64 {
476 self.0.num_seconds()
477 }
478
479 pub const fn milliseconds(&self) -> i64 {
480 self.0.num_milliseconds()
481 }
482
483 pub const fn nanoseconds(&self) -> i128 {
484 (self.0.num_seconds() as i128) * 1_000_000_000i128 + (self.0.subsec_nanos() as i128)
486 }
487
488 pub fn div_floor(&self, rhs: Self) -> i64 {
489 f64::floor(self.seconds_f64() / rhs.seconds_f64()) as i64
490 }
491
492 pub fn seconds_f64(&self) -> f64 {
494 let secs = self.0.num_seconds() as f64;
495 let nanos = self.0.subsec_nanos() as f64;
496 secs + (nanos / 1e9)
497 }
498
499 pub fn from_seconds_f64_lossy(seconds: f64) -> Self {
500 let millis = (seconds * (MILLISECOND_PER_SECOND as f64)) as i64;
501 Self::from_millis(millis)
502 }
503}
504
505#[cfg(feature = "serde")]
506impl serde::Serialize for Duration {
507 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
508 where
509 S: serde::Serializer,
510 {
511 use serde::ser::Error;
512 let duration = self.0;
513 let millis = duration.num_milliseconds();
514 let seconds = (millis as f64) / (MILLISECOND_PER_SECOND as f64);
515 if ((seconds * (MILLISECOND_PER_SECOND as f64)) as i64) != millis {
516 return Err(S::Error::custom("lossy duration serialization"));
517 }
518 serializer.serialize_f64(seconds)
519 }
520}
521
522#[cfg(feature = "serde")]
523impl<'de> serde::Deserialize<'de> for Duration {
524 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
525 where
526 D: serde::Deserializer<'de>,
527 {
528 use crate::types::DisplayErrorChain;
529 use serde::de::Error;
530
531 #[derive(serde::Deserialize)]
532 #[serde(untagged)]
533 enum StrOrNum<'s> {
534 Str(std::borrow::Cow<'s, str>),
535 Num(f64),
536 }
537
538 match StrOrNum::deserialize(deserializer)? {
539 StrOrNum::Str(s) => {
540 let std_duration = humantime::parse_duration(s.as_ref())
541 .map_err(|e| D::Error::custom(format!("invalid duration format: {}", DisplayErrorChain(&e))))?;
542 Self::from_std(std_duration)
543 .map_err(|e| D::Error::custom(format!("invalid duration: {}", DisplayErrorChain(&e))))
544 }
545 StrOrNum::Num(n) => Ok(Self::from_seconds_f64_lossy(n)),
546 }
547 }
548}
549
550impl ops::Div<i32> for Duration {
551 type Output = Self;
552
553 fn div(self, rhs: i32) -> Self::Output {
554 Self(self.0 / rhs)
555 }
556}
557
558impl ops::Mul<i32> for Duration {
559 type Output = Self;
560
561 fn mul(self, rhs: i32) -> Self::Output {
562 Self(self.0 * rhs)
563 }
564}
565
566#[cfg(feature = "serde")]
568#[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
569struct SerializablePeriodLower {
570 pub start: Instant,
571 pub end: Option<Instant>,
572}
573
574#[cfg(feature = "serde")]
575impl From<PeriodFrom> for SerializablePeriodLower {
576 fn from(period: PeriodFrom) -> Self {
577 Self {
578 start: period.start,
579 end: None,
580 }
581 }
582}
583
584#[cfg(feature = "serde")]
585impl From<FinitePeriod> for SerializablePeriodLower {
586 fn from(period: FinitePeriod) -> Self {
587 Self {
588 start: period.start,
589 end: Some(period.end),
590 }
591 }
592}
593
594#[cfg(feature = "serde")]
595impl From<PeriodLower> for SerializablePeriodLower {
596 fn from(period: PeriodLower) -> Self {
597 match period {
598 PeriodLower::From(p) => p.into(),
599 PeriodLower::Finite(p) => p.into(),
600 }
601 }
602}
603
604#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
605#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
606pub struct PeriodFrom {
607 pub start: Instant,
608}
609
610#[cfg(feature = "serde")]
611impl serde::Serialize for PeriodFrom {
612 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
613 where
614 S: serde::Serializer,
615 {
616 SerializablePeriodLower::from(*self).serialize(serializer)
617 }
618}
619
620impl From<RangeFrom<Instant>> for PeriodFrom {
621 fn from(r: RangeFrom<Instant>) -> Self {
622 Self { start: r.start }
623 }
624}
625
626impl From<PeriodFrom> for RangeFrom<Instant> {
627 fn from(r: PeriodFrom) -> Self {
628 r.start..
629 }
630}
631
632#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
639#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
640pub struct FinitePeriod {
641 pub start: Instant,
642 pub end: Instant,
643}
644
645impl FinitePeriod {
646 pub const fn new(start: Instant, end: Instant) -> Self {
647 Self { start, end }
648 }
649
650 pub const fn contains(self, time: Instant) -> bool {
652 matches!(self.start.const_cmp(time), Ordering::Less | Ordering::Equal)
653 && matches!(time.const_cmp(self.end), Ordering::Less)
654 }
655}
656
657#[cfg(feature = "serde")]
658impl serde::Serialize for FinitePeriod {
659 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
660 where
661 S: serde::Serializer,
662 {
663 SerializablePeriodLower::from(*self).serialize(serializer)
664 }
665}
666
667impl From<Range<Instant>> for FinitePeriod {
668 fn from(r: Range<Instant>) -> Self {
669 Self {
670 start: r.start,
671 end: r.end,
672 }
673 }
674}
675
676impl From<FinitePeriod> for Range<Instant> {
677 fn from(r: FinitePeriod) -> Self {
678 r.start..r.end
679 }
680}
681
682#[cfg(feature = "sqlx-postgres")]
683impl sqlx::Type<Postgres> for FinitePeriod {
684 fn type_info() -> postgres::PgTypeInfo {
685 PeriodLower::type_info()
686 }
687
688 fn compatible(ty: &postgres::PgTypeInfo) -> bool {
689 PeriodLower::compatible(ty)
690 }
691}
692
693#[cfg(feature = "sqlx-postgres")]
694#[derive(Debug, Error)]
695#[error("decoded invalid Postgres finite PERIOD_LOWER as {:?}", .0)]
696struct InvalidFinitePeriod(PgRange<Instant>);
697
698#[cfg(feature = "sqlx-postgres")]
699impl<'r> sqlx::Decode<'r, Postgres> for FinitePeriod {
700 fn decode(value: postgres::PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
701 let range = PgRange::<Instant>::decode(value)?;
702 match (range.start, range.end) {
703 (std::ops::Bound::Included(start), std::ops::Bound::Excluded(end)) => Ok(FinitePeriod { start, end }),
704 _ => Err(Box::new(InvalidFinitePeriod(range))),
705 }
706 }
707}
708
709#[cfg(feature = "sqlx-postgres")]
710impl sqlx::Encode<'_, Postgres> for FinitePeriod {
711 fn encode_by_ref(&self, buf: &mut postgres::PgArgumentBuffer) -> sqlx::encode::IsNull {
712 let range: PgRange<Instant> = (self.start..self.end).into();
713 range.encode_by_ref(buf)
714 }
715}
716
717#[cfg(feature = "sqlx-postgres")]
719struct PgPeriod;
720
721#[cfg(feature = "sqlx-postgres")]
722impl sqlx::Type<Postgres> for PgPeriod {
723 fn type_info() -> postgres::PgTypeInfo {
724 postgres::PgTypeInfo::with_name("period")
725 }
726
727 fn compatible(ty: &postgres::PgTypeInfo) -> bool {
728 *ty == Self::type_info()
729 }
730}
731
732#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(untagged))]
734#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
735pub enum PeriodLower {
736 Finite(FinitePeriod),
737 From(PeriodFrom),
738}
739
740impl PeriodLower {
741 pub const fn new(start: Instant, end: Option<Instant>) -> Self {
742 match end {
743 Some(end) => Self::bounded(start, end),
744 None => Self::unbounded(start),
745 }
746 }
747
748 pub const fn unbounded(start: Instant) -> Self {
749 Self::From(PeriodFrom { start })
750 }
751
752 pub const fn bounded(start: Instant, end: Instant) -> Self {
753 Self::Finite(FinitePeriod { start, end })
754 }
755
756 pub const fn start(self) -> Instant {
757 match self {
758 Self::Finite(p) => p.start,
759 Self::From(p) => p.start,
760 }
761 }
762
763 pub const fn end(self) -> Option<Instant> {
764 match self {
765 Self::Finite(p) => Some(p.end),
766 Self::From(_) => None,
767 }
768 }
769
770 pub fn end_min(self, other_end: Option<Instant>) -> Self {
772 if let Some(end) = other_end {
773 Self::Finite(self.bounded_end_min(end))
774 } else {
775 self
776 }
777 }
778
779 pub fn bounded_end_min(self, other_end: Instant) -> FinitePeriod {
781 match self {
782 Self::From(PeriodFrom { start }) => FinitePeriod { start, end: other_end },
783 Self::Finite(FinitePeriod { start, end }) => FinitePeriod {
784 start,
785 end: Instant::min(end, other_end),
786 },
787 }
788 }
789}
790
791#[cfg(feature = "serde")]
792impl serde::Serialize for PeriodLower {
793 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
794 where
795 S: serde::Serializer,
796 {
797 SerializablePeriodLower::from(*self).serialize(serializer)
798 }
799}
800
801#[cfg(feature = "sqlx-postgres")]
802impl sqlx::Type<Postgres> for PeriodLower {
803 fn type_info() -> postgres::PgTypeInfo {
804 postgres::PgTypeInfo::with_name("period_lower")
805 }
806
807 fn compatible(ty: &postgres::PgTypeInfo) -> bool {
808 *ty == Self::type_info() || *ty == PgPeriod::type_info() || *ty == (PgRange::<DateTime<Utc>>::type_info())
809 }
810}
811
812#[cfg(feature = "sqlx-postgres")]
813#[derive(Debug, Error)]
814#[error("decoded invalid Postgres PERIOD_LOWER as {:?}", .0)]
815struct InvalidPeriodLower(PgRange<Instant>);
816
817#[cfg(feature = "sqlx-postgres")]
818impl<'r> sqlx::Decode<'r, Postgres> for PeriodLower {
819 fn decode(value: postgres::PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
820 let range = PgRange::<Instant>::decode(value)?;
821 match (range.start, range.end) {
822 (std::ops::Bound::Included(start), std::ops::Bound::Unbounded) => Ok(PeriodLower::From(PeriodFrom { start })),
823 (std::ops::Bound::Included(start), std::ops::Bound::Excluded(end)) => {
824 Ok(PeriodLower::Finite(FinitePeriod { start, end }))
825 }
826 _ => Err(Box::new(InvalidPeriodLower(range))),
827 }
828 }
829}
830
831#[cfg(feature = "sqlx-postgres")]
832impl sqlx::Encode<'_, Postgres> for PeriodLower {
833 fn encode_by_ref(&self, buf: &mut postgres::PgArgumentBuffer) -> sqlx::encode::IsNull {
834 let range: PgRange<Instant> = match *self {
835 Self::Finite(FinitePeriod { start, end }) => (start..end).into(),
836 Self::From(PeriodFrom { start }) => (start..).into(),
837 };
838 range.encode_by_ref(buf)
839 }
840}
841
842#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
843#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
844pub struct UserDot {
845 pub time: Instant,
846 pub user: ShortUser,
847}
848
849#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
851#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
852pub struct BiRev<T> {
853 pub now: T,
854 pub then: T,
855}
856
857#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
859#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
860pub struct Value<T> {
861 pub value: T,
862}
863
864pub type BiValue<T> = BiRev<Value<T>>;
865
866#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
867#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
868pub struct RawUserDot {
869 pub time: Instant,
870 pub user: UserIdRef,
871}
872
873#[derive(Clone)]
875pub struct SecretString(Box<str>);
876
877impl SecretString {
878 pub fn new(str: String) -> Self {
879 Self(str.into_boxed_str())
880 }
881
882 pub fn as_str(&self) -> &str {
883 &self.0
884 }
885}
886
887#[derive(Clone)]
889pub struct SecretBytes(Box<[u8]>);
890
891impl SecretBytes {
892 pub fn new(bytes: Vec<u8>) -> Self {
893 Self(bytes.into_boxed_slice())
894 }
895
896 pub fn as_slice(&self) -> &[u8] {
897 &self.0
898 }
899}
900
901declare_new_int! {
902 pub struct IntPercentage(u8);
905 pub type RangeError = IntPercentageRangeError;
906 const BOUNDS = 0..=100;
907 type SqlType = PgU8;
908 const SQL_NAME = "int_percentage";
909}
910
911#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
912#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
913pub struct Listing<T> {
914 pub offset: u32,
915 pub limit: u32,
916 pub count: u32,
917 pub items: Vec<T>,
918}
919
920impl<T> Listing<T> {
921 pub fn map<B, F>(self, f: F) -> Listing<B>
922 where
923 F: FnMut(T) -> B,
924 {
925 Listing {
926 offset: self.offset,
927 limit: self.limit,
928 count: self.count,
929 items: self.items.into_iter().map(f).collect(),
930 }
931 }
932}
933
934#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
935#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
936pub struct ListingCount {
937 pub count: u32,
938}
939
940pub trait Request<Brand = ()> {
941 type Response;
942}
943
944#[async_trait]
945pub trait Handler<Req, Brand = ()>
946where
947 Req: Request<Brand>,
948{
949 async fn handle(&self, req: Req) -> Req::Response;
950}
951
952#[cfg(test)]
953mod test {
954 #[cfg(feature = "serde")]
955 use crate::core::{FinitePeriod, Instant, PeriodFrom, PeriodLower};
956 #[cfg(feature = "serde")]
957 use std::fs;
958
959 #[cfg(feature = "serde")]
960 #[allow(clippy::unnecessary_wraps)]
961 fn get_finite_period_one_millisecond() -> FinitePeriod {
962 FinitePeriod {
963 start: Instant::ymd_hms(2021, 1, 1, 0, 0, 0),
964 end: Instant::ymd_hms_milli(2021, 1, 1, 0, 0, 0, 1),
965 }
966 }
967
968 #[cfg(feature = "serde")]
969 #[test]
970 fn read_finite_period_one_millisecond() {
971 let s = fs::read_to_string("../../test-resources/core/core/finite-period/one-millisecond/value.json").unwrap();
972 let actual: FinitePeriod = serde_json::from_str(&s).unwrap();
973 let expected = get_finite_period_one_millisecond();
974 assert_eq!(actual, expected);
975 }
976
977 #[cfg(feature = "serde")]
978 #[test]
979 fn write_finite_period_one_millisecond() {
980 let value = get_finite_period_one_millisecond();
981 let actual: String = serde_json::to_string_pretty(&value).unwrap();
982 let expected =
983 fs::read_to_string("../../test-resources/core/core/finite-period/one-millisecond/value.json").unwrap();
984 assert_eq!(&actual, expected.trim());
985 }
986
987 #[cfg(feature = "serde")]
988 #[allow(clippy::unnecessary_wraps)]
989 fn get_finite_period_one_second() -> FinitePeriod {
990 FinitePeriod {
991 start: Instant::ymd_hms(2021, 1, 1, 0, 0, 0),
992 end: Instant::ymd_hms(2021, 1, 1, 0, 0, 1),
993 }
994 }
995
996 #[cfg(feature = "serde")]
997 #[test]
998 fn read_finite_period_one_second() {
999 let s = fs::read_to_string("../../test-resources/core/core/finite-period/one-second/value.json").unwrap();
1000 let actual: FinitePeriod = serde_json::from_str(&s).unwrap();
1001 let expected = get_finite_period_one_second();
1002 assert_eq!(actual, expected);
1003 }
1004
1005 #[cfg(feature = "serde")]
1006 #[test]
1007 fn write_finite_period_one_second() {
1008 let value = get_finite_period_one_second();
1009 let actual: String = serde_json::to_string_pretty(&value).unwrap();
1010 let expected = fs::read_to_string("../../test-resources/core/core/finite-period/one-second/value.json").unwrap();
1011 assert_eq!(&actual, expected.trim());
1012 }
1013
1014 #[cfg(feature = "serde")]
1015 #[allow(clippy::unnecessary_wraps)]
1016 fn get_period_from_unbounded() -> PeriodFrom {
1017 PeriodFrom {
1018 start: Instant::ymd_hms(2021, 1, 1, 0, 0, 0),
1019 }
1020 }
1021
1022 #[cfg(feature = "serde")]
1023 #[test]
1024 fn read_period_from_unbounded() {
1025 let s = fs::read_to_string("../../test-resources/core/core/period-from/unbounded/value.json").unwrap();
1026 let actual: PeriodFrom = serde_json::from_str(&s).unwrap();
1027 let expected = get_period_from_unbounded();
1028 assert_eq!(actual, expected);
1029 }
1030
1031 #[cfg(feature = "serde")]
1032 #[test]
1033 fn write_period_from_unbounded() {
1034 let value = get_period_from_unbounded();
1035 let actual: String = serde_json::to_string_pretty(&value).unwrap();
1036 let expected = fs::read_to_string("../../test-resources/core/core/period-from/unbounded/value.json").unwrap();
1037 assert_eq!(&actual, expected.trim());
1038 }
1039
1040 #[cfg(feature = "serde")]
1041 #[allow(clippy::unnecessary_wraps)]
1042 fn get_period_lower_one_millisecond() -> PeriodLower {
1043 PeriodLower::Finite(FinitePeriod {
1044 start: Instant::ymd_hms(2021, 1, 1, 0, 0, 0),
1045 end: Instant::ymd_hms_milli(2021, 1, 1, 0, 0, 0, 1),
1046 })
1047 }
1048
1049 #[cfg(feature = "serde")]
1050 #[test]
1051 fn read_period_lower_one_millisecond() {
1052 let s = fs::read_to_string("../../test-resources/core/core/period-lower/one-millisecond/value.json").unwrap();
1053 let actual: PeriodLower = serde_json::from_str(&s).unwrap();
1054 let expected = get_period_lower_one_millisecond();
1055 assert_eq!(actual, expected);
1056 }
1057
1058 #[cfg(feature = "serde")]
1059 #[test]
1060 fn write_period_lower_one_millisecond() {
1061 let value = get_period_lower_one_millisecond();
1062 let actual: String = serde_json::to_string_pretty(&value).unwrap();
1063 let expected =
1064 fs::read_to_string("../../test-resources/core/core/period-lower/one-millisecond/value.json").unwrap();
1065 assert_eq!(&actual, expected.trim());
1066 }
1067
1068 #[cfg(feature = "serde")]
1069 #[allow(clippy::unnecessary_wraps)]
1070 fn get_period_lower_one_second() -> PeriodLower {
1071 PeriodLower::Finite(FinitePeriod {
1072 start: Instant::ymd_hms(2021, 1, 1, 0, 0, 0),
1073 end: Instant::ymd_hms(2021, 1, 1, 0, 0, 1),
1074 })
1075 }
1076
1077 #[cfg(feature = "serde")]
1078 #[test]
1079 fn read_period_lower_one_second() {
1080 let s = fs::read_to_string("../../test-resources/core/core/period-lower/one-second/value.json").unwrap();
1081 let actual: PeriodLower = serde_json::from_str(&s).unwrap();
1082 let expected = get_period_lower_one_second();
1083 assert_eq!(actual, expected);
1084 }
1085
1086 #[cfg(feature = "serde")]
1087 #[test]
1088 fn write_period_lower_one_second() {
1089 let value = get_period_lower_one_second();
1090 let actual: String = serde_json::to_string_pretty(&value).unwrap();
1091 let expected = fs::read_to_string("../../test-resources/core/core/period-lower/one-second/value.json").unwrap();
1092 assert_eq!(&actual, expected.trim());
1093 }
1094
1095 #[cfg(feature = "serde")]
1096 #[allow(clippy::unnecessary_wraps)]
1097 fn get_period_lower_unbounded() -> PeriodLower {
1098 PeriodLower::unbounded(Instant::ymd_hms(2021, 1, 1, 0, 0, 0))
1099 }
1100
1101 #[cfg(feature = "serde")]
1102 #[test]
1103 fn read_period_lower_unbounded() {
1104 let s = fs::read_to_string("../../test-resources/core/core/period-lower/unbounded/value.json").unwrap();
1105 let actual: PeriodLower = serde_json::from_str(&s).unwrap();
1106 let expected = get_period_lower_unbounded();
1107 assert_eq!(actual, expected);
1108 }
1109
1110 #[cfg(feature = "serde")]
1111 #[test]
1112 fn write_period_lower_unbounded() {
1113 let value = get_period_lower_unbounded();
1114 let actual: String = serde_json::to_string_pretty(&value).unwrap();
1115 let expected = fs::read_to_string("../../test-resources/core/core/period-lower/unbounded/value.json").unwrap();
1116 assert_eq!(&actual, expected.trim());
1117 }
1118}