parse_mediawiki_sql/
field_types.rs

1/*!
2The types used in the [`schemas`](crate::schemas) module.
3
4Implements the [`FromSql`] trait for them.
5Re-exports the [`Datelike`] and [`Timelike`] traits from the [`chrono`] crate,
6which are used by [`Timestamp`].
7 */
8use nom::{
9    branch::alt,
10    bytes::streaming::tag,
11    combinator::{map, map_res},
12    error::context,
13};
14
15use std::{convert::TryFrom, ops::Deref, str::FromStr};
16
17#[cfg(feature = "serialization")]
18use serde::{Deserialize, Deserializer, Serialize, Serializer};
19
20#[cfg(test)]
21use bstr::B;
22
23use crate::from_sql::FromSql;
24use crate::from_sql::IResult;
25
26/// The type used for float fields that are never NaN.
27pub use ordered_float::NotNan;
28
29/// The type that [`Timestamp`] derefs to, from `chrono`.
30pub use chrono::NaiveDateTime;
31
32/// Trait for [`Timestamp`], re-exported from `chrono`.
33pub use chrono::{Datelike, Timelike};
34
35macro_rules! impl_wrapper {
36    // $l1 and $l2 must be identical.
37    (
38        $(#[$attrib:meta])*
39        $wrapper:ident<$l1:lifetime>: &$l2:lifetime $wrapped_type:ty
40    ) => {
41        impl_wrapper! {
42            @maybe_copy [&$l2 $wrapped_type]
43            $(#[$attrib])*
44            #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
45            pub struct $wrapper<$l1>(pub &$l2 $wrapped_type);
46
47            impl<$l1> FromSql<$l1> for $wrapper<$l1> {
48                fn from_sql(s: &$l1 [u8]) -> IResult<$l1, Self> {
49                    context(
50                        stringify!($wrapper),
51                        map(<&$l2 $wrapped_type>::from_sql, $wrapper)
52                    )(s)
53                }
54            }
55
56            #[allow(unused)]
57            impl<$l1> $wrapper<$l1> {
58                pub const fn into_inner(self) -> &$l2 $wrapped_type {
59                    self.0
60                }
61            }
62
63            impl<$l1> From<$wrapper<$l1>> for &$l2 $wrapped_type {
64                fn from(val: $wrapper<$l1>) -> Self {
65                    val.0
66                }
67            }
68
69            impl<$l1> From<&$l2 $wrapped_type> for $wrapper<$l1> {
70                fn from(val: &$l2 $wrapped_type) -> Self {
71                    Self(val)
72                }
73            }
74        }
75    };
76    (
77        $(#[$attrib:meta])*
78        $wrapper:ident: $wrapped:ident
79    ) => {
80        impl_wrapper! {
81            @maybe_copy [$wrapped]
82            $(#[$attrib])*
83            #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
84            pub struct $wrapper(pub $wrapped);
85
86            impl<'input> FromSql<'input> for $wrapper {
87                fn from_sql(s: &'input [u8]) -> IResult<'input, Self> {
88                    context(
89                        stringify!($wrapper),
90                        map(<$wrapped>::from_sql, $wrapper)
91                    )(s)
92                }
93            }
94
95            #[allow(unused)]
96            impl $wrapper {
97                pub fn into_inner(self) -> $wrapped {
98                    self.0
99                }
100            }
101
102            impl From<$wrapper> for $wrapped {
103                fn from(val: $wrapper) -> Self {
104                    val.0
105                }
106            }
107
108            impl<'a> From<&'a $wrapper> for &'a $wrapped {
109                fn from(val: &'a $wrapper) -> Self {
110                    &val.0
111                }
112            }
113
114            impl From<$wrapped> for $wrapper {
115                fn from(val: $wrapped) -> Self {
116                    Self(val)
117                }
118            }
119        }
120    };
121    (
122        @maybe_copy [$(u32)? $(i32)? $(&$l:lifetime $t:ty)?]
123        $($rest:item)+
124    ) => {
125        #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
126        $($rest)+
127    };
128    (
129        @maybe_copy [$($anything:tt)?]
130        $($rest:item)+
131    ) => {
132        #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
133        $($rest)+
134    };
135}
136
137impl_wrapper! {
138    #[doc = "
139Represents
140[`page_id`](https://www.mediawiki.org/wiki/Manual:Page_table#page_id),
141the primary key of the `page` table, as well as other fields in
142other tables that correspond to it.
143"]
144    PageId: u32
145}
146
147impl_wrapper! {
148    #[doc = "
149Represents the
150[`page_namespace`](https://www.mediawiki.org/wiki/Manual:Page_table#page_namespace)
151field of the `page` table.
152"]
153    PageNamespace: i32
154}
155
156impl_wrapper! {
157    #[doc="
158Represents the
159[`page_title`](https://www.mediawiki.org/wiki/Manual:Page_table#page_title)
160field of the `page` table, a title with underscores.
161"]
162    PageTitle: String
163}
164
165impl_wrapper! {
166    #[doc="
167Represents a page title with namespace and with spaces rather than underscores,
168as in the
169[`ll_title`](https://www.mediawiki.org/wiki/Manual:Langlinks_table#ll_title)
170field of the `langlinks` table.
171"]
172    FullPageTitle: String
173}
174
175impl_wrapper! {
176    #[doc="
177Represents [`lt_id`](https://www.mediawiki.org/wiki/Manual:Linktarget_table#lt_id),
178the primary key of the `linktarget` table.
179"]
180    LinkTargetId: u64
181}
182
183impl_wrapper! {
184    #[doc = "
185Represents
186[`cat_id`](https://www.mediawiki.org/wiki/Manual:Category_table#cat_id),
187the primary key of the `category` table.
188"]
189    CategoryId: u32
190}
191
192impl_wrapper! {
193    #[doc = "
194Represents
195[`cat_pages`](https://www.mediawiki.org/wiki/Manual:Category_table#cat_id),
196[`cat_subcats`](https://www.mediawiki.org/wiki/Manual:Category_table#cat_subcats),
197and [`cat_files`](https://www.mediawiki.org/wiki/Manual:Category_table#cat_files)
198fields of the `category` table. They should logically be greater than
199or equal to 0, but because of errors can be negative.
200"]
201    PageCount: i32
202}
203
204impl_wrapper! {
205    #[doc = "
206Represents
207[`log_id`](https://www.mediawiki.org/wiki/Manual:Logging_table#log_id),
208the primary key of the `logging` table.
209"]
210    LogId: u32
211}
212
213impl_wrapper! {
214    #[doc = "
215Represents
216[`ct_id`](https://www.mediawiki.org/wiki/Manual:Change_tag_table#ct_id),
217the primary key of the `change_tag` table.
218"]
219    ChangeTagId: u32
220}
221
222impl_wrapper! {
223    #[doc = "
224Represents
225[`rev_id`](https://www.mediawiki.org/wiki/Manual:Revision_table#rev_id),
226the primary key of the `revision` table.
227"]
228    RevisionId: u32
229}
230
231impl_wrapper! {
232    #[doc = "
233Represents
234[`ctd_id`](https://www.mediawiki.org/wiki/Manual:Change_tag_def_table#ctd_id),
235the primary key of the `change_tag_def` table.
236"]
237    ChangeTagDefinitionId: u32
238}
239
240impl_wrapper! {
241    #[doc = "
242Represents
243[`rc_id`](https://www.mediawiki.org/wiki/Manual:Recentchanges_table#rc_id),
244the primary key of the `recentchanges` table.
245"]
246    RecentChangeId: u32
247}
248
249impl_wrapper! {
250    #[doc = "
251Represents
252[`el_id`](https://www.mediawiki.org/wiki/Manual:Externallinks_table#el_id),
253the primary key of the `externallinks` table.
254"]
255    ExternalLinkId: u32
256}
257
258impl_wrapper! {
259    #[doc = "
260Represents the
261[`img_minor_mime`](https://www.mediawiki.org/wiki/Manual:Image_table#img_minor_mime)
262field of the `image` table.
263"]
264    MinorMime<'a>: &'a str
265}
266
267impl_wrapper! {
268    #[doc = "
269Represents
270[`comment_id`](https://www.mediawiki.org/wiki/Manual:Comment_table#comment_id),
271the primary key of the `comment` table.
272"]
273    CommentId: u32
274}
275
276impl_wrapper! {
277    #[doc = "
278Represents
279[`actor_id`](https://www.mediawiki.org/wiki/Manual:Actor_table#actor_id),
280the primary key of the `actor` table.
281"]
282    ActorId: u32
283}
284
285impl_wrapper! {
286    #[doc = "
287Represents a SHA-1 hash in base 36, for instance in the
288[`img_sha1`](https://www.mediawiki.org/wiki/Manual:Image_table#img_sha1)
289field of the `image` table.
290"]
291    Sha1<'a>: &'a str
292}
293
294impl_wrapper! {
295    #[doc = "
296Represents
297[`pr_id`](https://www.mediawiki.org/wiki/Manual:Page_restrictions_table#pr_id),
298the primary key of the `page_restrictions` table.
299"]
300    PageRestrictionId: u32
301}
302
303impl_wrapper! {
304    #[doc = "
305Represents
306[`user_id`](https://www.mediawiki.org/wiki/Manual:User_table#user_id),
307the primary key of the `user` table.
308"]
309    UserId: u32
310}
311
312impl_wrapper! {
313    #[doc = "
314Represents the name of a user group, such as the
315[`ug_group`](https://www.mediawiki.org/wiki/Manual:User_groups_table#ug_group)
316field of the `user_groups` table.
317"]
318    UserGroup<'a>: &'a str
319}
320
321#[test]
322fn test_copy_for_wrappers() {
323    use static_assertions::*;
324    assert_impl_all!(PageId: Copy);
325    assert_not_impl_all!(PageTitle: Copy);
326    assert_impl_all!(PageNamespace: Copy);
327    assert_impl_all!(UserGroup: Copy);
328}
329
330/// A [timestamp](https://www.mediawiki.org/wiki/Manual:Timestamp),
331/// represented as a string in the format `'yyyymmddhhmmss'` or `'yyyy-mm-dd hh:mm::ss'`.
332/// Provides the methods of [`NaiveDateTime`] through [`Deref`].
333#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
334#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
335pub struct Timestamp(pub NaiveDateTime);
336
337impl<'input> FromSql<'input> for Timestamp {
338    fn from_sql(s: &'input [u8]) -> IResult<'input, Self> {
339        context(
340            "Timestamp in yyyymmddhhmmss or yyyy-mm-dd hh:mm::ss format",
341            map_res(<&str>::from_sql, |s| {
342                NaiveDateTime::parse_from_str(
343                    s,
344                    if s.len() == 14 {
345                        "%Y%m%d%H%M%S"
346                    } else {
347                        "%Y-%m-%d %H:%M:%S"
348                    },
349                )
350                .map(Timestamp)
351            }),
352        )(s)
353    }
354}
355
356impl Deref for Timestamp {
357    type Target = NaiveDateTime;
358
359    fn deref(&self) -> &Self::Target {
360        &self.0
361    }
362}
363
364/// Represents the
365/// [`pr_expiry`](https://www.mediawiki.org/wiki/Manual:Page_restrictions_table#pr_expiry)
366/// field of the `page_restrictions` table.
367#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
368#[cfg_attr(
369    feature = "serialization",
370    derive(Serialize, Deserialize),
371    serde(try_from = "&str", into = "String")
372)]
373pub enum Expiry {
374    Timestamp(Timestamp),
375    Infinity,
376}
377
378impl TryFrom<&str> for Expiry {
379    type Error = <NaiveDateTime as FromStr>::Err;
380
381    fn try_from(s: &str) -> Result<Self, Self::Error> {
382        match s {
383            "infinity" => Ok(Expiry::Infinity),
384            s => Ok(Expiry::Timestamp(Timestamp(s.parse()?))),
385        }
386    }
387}
388
389impl From<Expiry> for String {
390    fn from(e: Expiry) -> Self {
391        match e {
392            Expiry::Timestamp(t) => t.to_string(),
393            Expiry::Infinity => "infinity".to_string(),
394        }
395    }
396}
397
398impl<'input> FromSql<'input> for Expiry {
399    fn from_sql(s: &'input [u8]) -> IResult<'input, Self> {
400        context(
401            "Expiry",
402            alt((
403                map(Timestamp::from_sql, Expiry::Timestamp),
404                context("“infinity”", map(tag("'infinity'"), |_| Expiry::Infinity)),
405            )),
406        )(s)
407    }
408}
409
410// #[cfg(feature = "serialization")]
411// impl Serialize for Expiry {
412//     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
413//     where
414//         S: Serializer,
415//     {
416//         match self {
417//             Expiry::Timestamp(timestamp) => timestamp.serialize(serializer),
418//             Expiry::Infinity => timestamp.serialize_str("infinity"),
419//         }
420//     }
421// }
422
423// #[cfg(feature = "serialization")]
424// impl<'de> Deserialize<'de> for Expiry {
425//     fn deserialize<D>(deserializer: D) -> Result<Expiry, D::Error>
426//     where
427//         D: Deserializer<'de>,
428//     {
429//         match deserializer.deserialize_str(I32Visitor)? {
430//             "infinity" => Ok(Expiry::Infinity),
431//             s => Ok(Timestamp::from_str(s)?),
432//         }
433//     }
434// }
435
436/// Represents the
437/// [`cl_type`](https://www.mediawiki.org/wiki/Manual:Categorylinks_table#cl_type)
438/// field of the `categorylinks` table.
439#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
440#[cfg_attr(
441    feature = "serialization",
442    derive(Serialize, Deserialize),
443    serde(try_from = "&str", into = "&'static str")
444)]
445pub enum PageType {
446    Page,
447    Subcat,
448    File,
449}
450
451/// Returns the unrecognized string as the error value.
452impl<'a> TryFrom<&'a str> for PageType {
453    type Error = &'a str;
454
455    fn try_from(s: &'a str) -> Result<Self, &'a str> {
456        use PageType::*;
457        match s {
458            "page" => Ok(Page),
459            "subcat" => Ok(Subcat),
460            "file" => Ok(File),
461            other => Err(other),
462        }
463    }
464}
465
466impl From<PageType> for &'static str {
467    fn from(s: PageType) -> &'static str {
468        use PageType::*;
469        match s {
470            Page => "page",
471            Subcat => "subcat",
472            File => "file",
473        }
474    }
475}
476
477impl<'a> FromSql<'a> for PageType {
478    fn from_sql(s: &'a [u8]) -> IResult<'a, Self> {
479        context("PageType", map_res(<&str>::from_sql, PageType::try_from))(s)
480    }
481}
482
483/// Represents the
484/// [`pr_type`](https://www.mediawiki.org/wiki/Manual:Page_restrictions_table#pr_type)
485/// field of the `page_restrictions` table, the action that is restricted.
486#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
487#[cfg_attr(
488    feature = "serialization",
489    derive(Serialize, Deserialize),
490    serde(from = "&'a str", into = "&'a str")
491)]
492pub enum PageAction<'a> {
493    Edit,
494    Move,
495    Reply,
496    Upload,
497    All,
498    Other(&'a str),
499}
500
501impl<'a> From<&'a str> for PageAction<'a> {
502    fn from(s: &'a str) -> Self {
503        use PageAction::*;
504        match s {
505            "edit" => Edit,
506            "move" => Move,
507            "reply" => Reply,
508            "upload" => Upload,
509            _ => Other(s),
510        }
511    }
512}
513
514impl<'a> From<PageAction<'a>> for &'a str {
515    fn from(p: PageAction<'a>) -> Self {
516        use PageAction::*;
517        match p {
518            Edit => "edit",
519            Move => "move",
520            Reply => "reply",
521            Upload => "upload",
522            All => "all",
523            Other(s) => s,
524        }
525    }
526}
527
528impl<'a> FromSql<'a> for PageAction<'a> {
529    fn from_sql(s: &'a [u8]) -> IResult<'a, Self> {
530        map(<&str>::from_sql, PageAction::from)(s)
531    }
532}
533
534/// Represents the
535/// [`pr_level`](https://www.mediawiki.org/wiki/Manual:Page_restrictions_table#pr_level)
536/// field of the `page_restrictions` table, the group that is allowed
537/// to perform the action.
538#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
539#[cfg_attr(
540    feature = "serialization",
541    derive(Serialize, Deserialize),
542    serde(from = "&'a str", into = "&'a str")
543)]
544pub enum ProtectionLevel<'a> {
545    Autoconfirmed,
546    ExtendedConfirmed,
547    Sysop,
548    TemplateEditor,
549    EditProtected,
550    EditSemiProtected,
551    /// The result of parsing the empty string after the `=` in `'move=:edit='`.
552    None,
553    Other(&'a str),
554}
555
556impl<'a> From<&'a str> for ProtectionLevel<'a> {
557    fn from(s: &'a str) -> Self {
558        use ProtectionLevel::*;
559        match s {
560            "autoconfirmed" => Autoconfirmed,
561            "extendedconfirmed" => ExtendedConfirmed,
562            "templateeditor" => TemplateEditor,
563            "sysop" => Sysop,
564            "editprotected" => EditProtected,
565            "editsemiprotected" => EditSemiProtected,
566            "" => None,
567            _ => Other(s),
568        }
569    }
570}
571
572impl<'a> From<ProtectionLevel<'a>> for &'a str {
573    fn from(p: ProtectionLevel<'a>) -> &'a str {
574        use ProtectionLevel::*;
575        match p {
576            Autoconfirmed => "autoconfirmed",
577            ExtendedConfirmed => "extendedconfirmed",
578            TemplateEditor => "templateeditor",
579            Sysop => "sysop",
580            EditProtected => "editprotected",
581            EditSemiProtected => "editsemiprotected",
582            None => "",
583            Other(s) => s,
584        }
585    }
586}
587
588impl<'a> FromSql<'a> for ProtectionLevel<'a> {
589    fn from_sql(s: &'a [u8]) -> IResult<'a, Self> {
590        context(
591            "ProtectionLevel",
592            map(<&str>::from_sql, ProtectionLevel::from),
593        )(s)
594    }
595}
596
597/// Represents the
598/// [`page_content_model`](https://www.mediawiki.org/wiki/Manual:Page_table#page_content_model)
599/// field of the `page` table.
600#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
601#[cfg_attr(
602    feature = "serialization",
603    derive(Serialize, Deserialize),
604    serde(from = "&'a str", into = "&'a str")
605)]
606pub enum ContentModel<'a> {
607    Wikitext,
608    Scribunto,
609    Text,
610    Css,
611    SanitizedCss,
612    JavaScript,
613    Json,
614    #[cfg_attr(feature = "serialization", serde(borrow))]
615    Other(&'a str),
616}
617
618impl<'a> From<&'a str> for ContentModel<'a> {
619    fn from(s: &'a str) -> Self {
620        use ContentModel::*;
621        match s {
622            "wikitext" => Wikitext,
623            "Scribunto" => Scribunto,
624            "text" => Text,
625            "css" => Css,
626            "sanitized-css" => SanitizedCss,
627            "javascript" => JavaScript,
628            "json" => Json,
629            _ => Other(s),
630        }
631    }
632}
633
634impl<'a> From<ContentModel<'a>> for &'a str {
635    fn from(c: ContentModel<'a>) -> Self {
636        use ContentModel::*;
637        match c {
638            Wikitext => "wikitext",
639            Scribunto => "Scribunto",
640            Text => "text",
641            Css => "css",
642            SanitizedCss => "sanitized-css",
643            JavaScript => "javascript",
644            Json => "json",
645            Other(s) => s,
646        }
647    }
648}
649
650impl<'a> FromSql<'a> for ContentModel<'a> {
651    fn from_sql(s: &'a [u8]) -> IResult<'a, Self> {
652        context("ContentModel", map(<&str>::from_sql, ContentModel::from))(s)
653    }
654}
655
656/// Represents the
657/// [`img_media_type`](https://www.mediawiki.org/wiki/Manual:Image_table#img_media_type)
658/// field of the `image` table.
659#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
660#[cfg_attr(
661    feature = "serialization",
662    derive(Serialize, Deserialize),
663    serde(from = "&'a str", into = "&'a str")
664)]
665pub enum MediaType<'a> {
666    Unknown,
667    Bitmap,
668    Drawing,
669    Audio,
670    Video,
671    Multimedia,
672    Office,
673    Text,
674    Executable,
675    Archive,
676    ThreeDimensional,
677    #[cfg_attr(feature = "serialization", serde(borrow))]
678    Other(&'a str),
679}
680
681impl<'a> From<&'a str> for MediaType<'a> {
682    fn from(s: &'a str) -> Self {
683        use MediaType::*;
684        match s {
685            "UNKNOWN" => Unknown,
686            "BITMAP" => Bitmap,
687            "DRAWING" => Drawing,
688            "AUDIO" => Audio,
689            "VIDEO" => Video,
690            "MULTIMEDIA" => Multimedia,
691            "OFFICE" => Office,
692            "TEXT" => Text,
693            "EXECUTABLE" => Executable,
694            "ARCHIVE" => Archive,
695            "3D" => ThreeDimensional,
696            _ => Other(s),
697        }
698    }
699}
700
701impl<'a> From<MediaType<'a>> for &'a str {
702    fn from(s: MediaType<'a>) -> Self {
703        use MediaType::*;
704        match s {
705            Unknown => "UNKNOWN",
706            Bitmap => "BITMAP",
707            Drawing => "DRAWING",
708            Audio => "AUDIO",
709            Video => "VIDEO",
710            Multimedia => "MULTIMEDIA",
711            Office => "OFFICE",
712            Text => "TEXT",
713            Executable => "EXECUTABLE",
714            Archive => "ARCHIVE",
715            ThreeDimensional => "3D",
716            Other(s) => s,
717        }
718    }
719}
720
721impl<'a> FromSql<'a> for MediaType<'a> {
722    fn from_sql(s: &'a [u8]) -> IResult<'a, Self> {
723        context("MediaType", map(<&str>::from_sql, MediaType::from))(s)
724    }
725}
726
727/// Represents the
728/// [`img_major_mime`](https://www.mediawiki.org/wiki/Manual:Image_table#img_major_mime)
729/// field of the `image` table.
730#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
731#[cfg_attr(
732    feature = "serialization",
733    derive(Serialize, Deserialize),
734    serde(from = "&'a str", into = "&'a str")
735)]
736pub enum MajorMime<'a> {
737    Unknown,
738    Application,
739    Audio,
740    Image,
741    Text,
742    Video,
743    Message,
744    Model,
745    Multipart,
746    #[cfg_attr(feature = "serialization", serde(borrow))]
747    Other(&'a str),
748}
749
750impl<'a> From<&'a str> for MajorMime<'a> {
751    fn from(s: &'a str) -> Self {
752        use MajorMime::*;
753        match s {
754            "unknown" => Unknown,
755            "application" => Application,
756            "audio" => Audio,
757            "image" => Image,
758            "text" => Text,
759            "video" => Video,
760            "message" => Message,
761            "model" => Model,
762            "multipart" => Multipart,
763            _ => Other(s),
764        }
765    }
766}
767
768impl<'a> From<MajorMime<'a>> for &'a str {
769    fn from(s: MajorMime<'a>) -> Self {
770        use MajorMime::*;
771        match s {
772            Unknown => "unknown",
773            Application => "application",
774            Audio => "audio",
775            Image => "image",
776            Text => "text",
777            Video => "video",
778            Message => "message",
779            Model => "model",
780            Multipart => "multipart",
781            Other(s) => s,
782        }
783    }
784}
785
786impl<'a> FromSql<'a> for MajorMime<'a> {
787    fn from_sql(s: &'a [u8]) -> IResult<'a, Self> {
788        context("MajorMime", map(<&str>::from_sql, MajorMime::from))(s)
789    }
790}
791
792#[test]
793fn test_bool() {
794    for (s, v) in &[(B("0"), false), (B("1"), true)] {
795        assert_eq!(bool::from_sql(s), Ok((B(""), *v)));
796    }
797}
798
799#[test]
800fn test_numbers() {
801    fn from_utf8(s: &[u8]) -> &str {
802        std::str::from_utf8(s).unwrap()
803    }
804
805    // Add a space to the end to avoid `nom::Err::Incomplete`.
806    let f = B("0.37569 ");
807    let res = f64::from_sql(f);
808    assert_eq!(res, Ok((B(" "), from_utf8(f).trim_end().parse().unwrap())));
809
810    for i in &[B("1 "), B("-1 ")] {
811        assert_eq!(
812            i32::from_sql(i),
813            Ok((B(" "), from_utf8(i).trim_end().parse().unwrap()))
814        );
815    }
816}
817
818#[test]
819fn test_string() {
820    let strings = &[
821        (B(r"'\''"), r"'"),
822        (br"'\\'", r"\"),
823        (br"'\n'", "\n"),
824        (br"'string'", r"string"),
825        (
826            br#"'English_words_ending_in_\"-vorous\",_\"-phagous\"_and_similar_endings'"#,
827            r#"English_words_ending_in_"-vorous",_"-phagous"_and_similar_endings"#,
828        ),
829    ];
830    for (s, unescaped) in strings {
831        assert_eq!(String::from_sql(s), Ok((B(""), (*unescaped).to_string())));
832    }
833}
834
835#[cfg(feature = "serialization")]
836pub(crate) fn serialize_not_nan<S>(not_nan: &NotNan<f64>, serializer: S) -> Result<S::Ok, S::Error>
837where
838    S: Serializer,
839{
840    serializer.serialize_f64(not_nan.into_inner())
841}
842
843#[cfg(feature = "serialization")]
844pub(crate) fn deserialize_not_nan<'de, D>(deserializer: D) -> Result<NotNan<f64>, D::Error>
845where
846    D: Deserializer<'de>,
847{
848    NotNan::new(f64::deserialize(deserializer)?).map_err(serde::de::Error::custom)
849}
850
851#[cfg(feature = "serialization")]
852pub(crate) fn serialize_option_not_nan<S>(
853    not_nan: &Option<NotNan<f64>>,
854    serializer: S,
855) -> Result<S::Ok, S::Error>
856where
857    S: Serializer,
858{
859    not_nan.map(|n| n.into_inner()).serialize(serializer)
860}
861
862#[cfg(feature = "serialization")]
863pub(crate) fn deserialize_option_not_nan<'de, D>(
864    deserializer: D,
865) -> Result<Option<NotNan<f64>>, D::Error>
866where
867    D: Deserializer<'de>,
868{
869    <Option<f64>>::deserialize(deserializer)?
870        .map(|v| NotNan::new(v).map_err(serde::de::Error::custom))
871        .transpose()
872}