Repository: jonasbb/serde_with
Pull request #655 (2023-10-12 16:55:33)

Add support for core::ops::Bound
I have encountered a case where I would like to use `Bound<Cow<'a, str>>` and borrow the string from the deserialized buffer instead of copying. To use `BorrowStr`, I had to add support for `core::ops::Bound` to `serde_with`.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/jonasbb/serde_with/pull/655?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Bushart) Report
> Merging [#655](https://app.codecov.io/gh/jonasbb/serde_with/pull/655?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Bushart) (e3991f9) into [master](https://app.codecov.io/gh/jonasbb/serde_with/commit/04bf8f35425dd81b4d6107ee9de9292ef128bc02?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Bushart) (04bf8f3) will **decrease** coverage by `0.30%`.
> The diff coverage is `0.00%`.

```diff
@@            Coverage Diff             @@
##           master     #655      +/-   ##
==========================================
- Coverage   64.85%   64.56%   -0.30%     
==========================================
  Files          36       36              
  Lines        2177     2187      +10     
==========================================
  Hits         1412     1412              
- Misses        765      775      +10     
```


| [Files](https://app.codecov.io/gh/jonasbb/serde_with/pull/655?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Bushart) | Coverage Δ | |
|---|---|---|
| [serde\_with/src/de/impls.rs](https://app.codecov.io/gh/jonasbb/serde_with/pull/655?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Bushart#diff-c2VyZGVfd2l0aC9zcmMvZGUvaW1wbHMucnM=) | `59.00% <0.00%> (-0.60%)` | :arrow_down: |
| [serde\_with/src/ser/impls.rs](https://app.codecov.io/gh/jonasbb/serde_with/pull/655?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Bushart#diff-c2VyZGVfd2l0aC9zcmMvc2VyL2ltcGxzLnJz) | `88.59% <0.00%> (-3.08%)` | :arrow_down: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Bushart)

jonasbb: Thanks for the PR. It makes sense to support Bounds. The code needs some updates, especially the tests. The trait implementations look OK. 

Patch:
diff --git a/serde_with/src/de/impls.rs b/serde_with/src/de/impls.rs
index 78357fd..ed25d9d 100644
--- a/serde_with/src/de/impls.rs
+++ b/serde_with/src/de/impls.rs
@@ -165,6 +165,24 @@ where
     }
 }
 
+impl<'de, T, U> DeserializeAs<'de, Bound<T>> for Bound<U>
+where
+    U: DeserializeAs<'de, T>,
+{
+    fn deserialize_as<D>(deserializer: D) -> Result<Bound<T>, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        Ok(
+            match Bound::<DeserializeAsWrap<T, U>>::deserialize(deserializer)? {
+                Bound::Unbounded => Bound::Unbounded,
+                Bound::Included(v) => Bound::Included(v.into_inner()),
+                Bound::Excluded(v) => Bound::Excluded(v.into_inner()),
+            },
+        )
+    }
+}
+
 #[cfg(feature = "alloc")]
 impl<'de, T, U> DeserializeAs<'de, Rc<T>> for Rc<U>
 where
diff --git a/serde_with/src/lib.rs b/serde_with/src/lib.rs
index dd1b271..05f6913 100644
--- a/serde_with/src/lib.rs
+++ b/serde_with/src/lib.rs
@@ -403,6 +403,7 @@ pub(crate) mod prelude {
         fmt::{self, Display},
         hash::{BuildHasher, Hash},
         marker::PhantomData,
+        ops::Bound,
         option::Option,
         result::Result,
         str::FromStr,
diff --git a/serde_with/src/ser/impls.rs b/serde_with/src/ser/impls.rs
index c6c37dd..dae073e 100644
--- a/serde_with/src/ser/impls.rs
+++ b/serde_with/src/ser/impls.rs
@@ -130,6 +130,24 @@ where
     }
 }
 
+impl<T, U> SerializeAs<Bound<T>> for Bound<U>
+where
+    U: SerializeAs<T>,
+    T: Sized,
+{
+    fn serialize_as<S>(source: &Bound<T>, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        match source {
+            Bound::Unbounded => Bound::Unbounded,
+            Bound::Included(ref v) => Bound::Included(SerializeAsWrap::<T, U>::new(v)),
+            Bound::Excluded(ref v) => Bound::Excluded(SerializeAsWrap::<T, U>::new(v)),
+        }
+        .serialize(serializer)
+    }
+}
+
 #[cfg(feature = "alloc")]
 impl<T, U> SerializeAs<Rc<T>> for Rc<U>
 where


Test patch:
diff --git a/serde_with/tests/serde_as/lib.rs b/serde_with/tests/serde_as/lib.rs
index 23e1ba8..953b5ab 100644
--- a/serde_with/tests/serde_as/lib.rs
+++ b/serde_with/tests/serde_as/lib.rs
@@ -29,6 +29,7 @@ use alloc::{
     sync::{Arc, Weak as ArcWeak},
 };
 use core::cell::{Cell, RefCell};
+use core::ops::Bound;
 use expect_test::expect;
 use serde::{Deserialize, Serialize};
 use serde_with::{
@@ -136,6 +137,41 @@ fn test_option() {
     );
 }
 
+#[test]
+fn test_bound() {
+    #[serde_as]
+    #[derive(Debug, Serialize, Deserialize, PartialEq)]
+    struct S(#[serde_as(as = "Bound<DisplayFromStr>")] Bound<u32>);
+
+    is_equal(S(Bound::Unbounded), expect![[r#""Unbounded""#]]);
+    is_equal(
+        S(Bound::Included(42)),
+        expect![[r#"
+        {
+          "Included": "42"
+        }"#]],
+    );
+    is_equal(
+        S(Bound::Excluded(42)),
+        expect![[r#"
+        {
+          "Excluded": "42"
+        }"#]],
+    );
+    check_error_deserialization::<S>(r#"{}"#, expect![[r#"expected value at line 1 column 2"#]]);
+
+    #[serde_as]
+    #[derive(Debug, Serialize, Deserialize, PartialEq)]
+    struct Struct {
+        #[serde_as(as = "Bound<DisplayFromStr>")]
+        value: Bound<u32>,
+    }
+    check_error_deserialization::<Struct>(
+        r#"{}"#,
+        expect![[r#"missing field `value` at line 1 column 2"#]],
+    );
+}
+
 #[test]
 fn test_result() {
     #[serde_as]