Skip to content

Commit 86b7001

Browse files
authored
Merge pull request #4 from MidasLamb/serde-support
Serde support
2 parents affc715 + e804060 commit 86b7001

File tree

6 files changed

+177
-10
lines changed

6 files changed

+177
-10
lines changed

.github/workflows/coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
uses: actions-rs/[email protected]
2727
with:
2828
version: "0.15.0"
29-
args: "-- --test-threads 1"
29+
args: "--all-features -- --test-threads 1"
3030

3131
- name: Upload to codecov.io
3232
uses: codecov/[email protected]

.github/workflows/generic.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ jobs:
2727
uses: actions-rs/cargo@v1
2828
with:
2929
command: check
30+
args: --all-features
3031

3132
test:
3233
name: Test Suite
@@ -46,6 +47,7 @@ jobs:
4647
uses: actions-rs/cargo@v1
4748
with:
4849
command: test
50+
args: --all-features
4951

5052
lints:
5153
name: Lints

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Changelog
2+
All notable changes to this project will be documented in this file.
3+
4+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [Unreleased]
8+
### Added
9+
10+
### Changed
11+
12+
### Removed
13+
14+
## [0.2.0]
15+
### Added
16+
* `serde` support behind the `serde` feature flag.
17+
* `Eq, PartialEq, Ord, PartialOrd` are now implemented for `NonEmptyString`.
18+
* `get` to retrieve a reference to the inner value.
19+
20+
### Changed
21+
* `new` constructor now returns a `Result` rather than an `Option`, which contains the original string
22+
23+
### Removed
24+
25+
[Unreleased]: https://github.com/MidasLamb/non-empty-string/v0.2.0...HEAD
26+
[0.2.0]: https://github.com/MidasLamb/non-empty-string/compare/v0.1.0...v0.2.0

Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
[package]
22
name = "non-empty-string"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2018"
55
authors = ["Midas Lambrichts <[email protected]>"]
66
license = "MIT OR Apache-2.0"
77
description = "A simple type for non empty Strings, similar to NonZeroUsize and friends."
88
repository = "https://github.com/MidasLamb/non-empty-string"
9-
keywords = ["nonemptystring", "string", "str"]
9+
keywords = ["nonemptystring", "string", "str", "non-empty", "nonempty"]
1010

1111
[lib]
1212
name = "non_empty_string"
1313

1414
[dependencies]
15+
serde = { version = "1", optional = true }
1516

1617
[dev-dependencies]
1718
assert_matches = "1.5.0"
19+
serde_json = { version = "1" }
20+
21+
[features]
22+
default = []
23+
serde = ["dep:serde"]

src/lib.rs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
11
#![doc = include_str!("../README.md")]
22

3+
#[cfg(feature = "serde")]
4+
mod serde_support;
5+
36
/// A simple String wrapper type, similar to NonZeroUsize and friends.
47
/// Guarantees that the String contained inside is not of length 0.
5-
#[derive(Debug, Clone)]
8+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
9+
#[repr(transparent)]
610
pub struct NonEmptyString(String);
711

812
impl NonEmptyString {
913
/// Attempts to create a new NonEmptyString.
10-
/// If the given `string` is empty, `None` is returned, `Some` otherwise.
11-
pub fn new(string: String) -> Option<NonEmptyString> {
14+
/// If the given `string` is empty, `Err` is returned, containing the original `String`, `Ok` otherwise.
15+
pub fn new(string: String) -> Result<NonEmptyString, String> {
1216
if string.is_empty() {
13-
None
17+
Err(string)
1418
} else {
15-
Some(NonEmptyString(string))
19+
Ok(NonEmptyString(string))
1620
}
1721
}
1822

23+
/// Returns a reference to the contained value.
24+
pub fn get(&self) -> &str {
25+
&self.0
26+
}
27+
28+
/// Consume the `NonEmptyString` to get the internal `String` out.
1929
pub fn into_inner(self) -> String {
2030
self.0
2131
}
@@ -33,19 +43,31 @@ impl std::convert::AsRef<String> for NonEmptyString {
3343
}
3444
}
3545

46+
impl<'s> std::convert::TryFrom<&'s str> for NonEmptyString {
47+
type Error = ();
48+
49+
fn try_from(value: &'s str) -> Result<Self, Self::Error> {
50+
if value.is_empty() {
51+
Err(())
52+
} else {
53+
Ok(NonEmptyString::new(value.to_owned()).expect("Value is not empty"))
54+
}
55+
}
56+
}
57+
3658
#[cfg(test)]
3759
mod tests {
3860
use super::*;
3961
use assert_matches::assert_matches;
4062

4163
#[test]
4264
fn empty_string_returns_none() {
43-
assert_matches!(NonEmptyString::new("".to_owned()), None);
65+
assert_eq!(NonEmptyString::new("".to_owned()), Err("".to_owned()));
4466
}
4567

4668
#[test]
4769
fn non_empty_string_returns_some() {
48-
assert_matches!(NonEmptyString::new("string".to_owned()), Some(_));
70+
assert_matches!(NonEmptyString::new("string".to_owned()), Ok(_));
4971
}
5072

5173
#[test]
@@ -57,4 +79,25 @@ mod tests {
5779
"string".to_owned()
5880
);
5981
}
82+
83+
#[test]
84+
fn as_ref_str_works() {
85+
let nes = NonEmptyString::new("string".to_owned()).unwrap();
86+
let val: &str = nes.as_ref();
87+
assert_eq!(val, "string");
88+
}
89+
90+
#[test]
91+
fn as_ref_string_works() {
92+
let nes = NonEmptyString::new("string".to_owned()).unwrap();
93+
let val: &String = nes.as_ref();
94+
assert_eq!(val, "string");
95+
}
96+
97+
#[test]
98+
fn calling_string_methods_works() {
99+
let nes = NonEmptyString::new("string".to_owned()).unwrap();
100+
// `len` is a `String` method.
101+
assert!(nes.get().len() > 0);
102+
}
60103
}

src/serde_support.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use std::fmt;
2+
3+
use serde::{
4+
de::{self, Unexpected, Visitor},
5+
ser::Serialize,
6+
};
7+
8+
use crate::NonEmptyString;
9+
10+
impl Serialize for NonEmptyString {
11+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
12+
where
13+
S: serde::Serializer,
14+
{
15+
serializer.serialize_str(self.get())
16+
}
17+
}
18+
19+
struct NonEmptyStringVisitor;
20+
21+
impl<'de> de::Deserialize<'de> for NonEmptyString {
22+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
23+
where
24+
D: serde::Deserializer<'de>,
25+
{
26+
deserializer.deserialize_string(NonEmptyStringVisitor)
27+
}
28+
}
29+
30+
pub enum DeserializeError {}
31+
32+
type Result<T, E = DeserializeError> = std::result::Result<T, E>;
33+
34+
impl<'de> Visitor<'de> for NonEmptyStringVisitor {
35+
type Value = NonEmptyString;
36+
37+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
38+
formatter.write_str("an integer between -2^31 and 2^31")
39+
}
40+
41+
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
42+
where
43+
E: de::Error,
44+
{
45+
self.visit_string(value.to_owned())
46+
}
47+
48+
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
49+
where
50+
E: de::Error,
51+
{
52+
NonEmptyString::new(value).map_err(|e| de::Error::invalid_value(Unexpected::Str(&e), &self))
53+
}
54+
}
55+
56+
#[cfg(test)]
57+
mod tests {
58+
use super::*;
59+
use crate::*;
60+
use assert_matches::assert_matches;
61+
use serde_json::json;
62+
63+
#[test]
64+
fn serialize_works() {
65+
let value = NonEmptyString("abc".to_owned());
66+
let result = serde_json::to_string(&value);
67+
68+
assert!(result.is_ok());
69+
70+
let json = serde_json::to_string(&json!("abc")).unwrap();
71+
assert_eq!(result.unwrap(), json)
72+
}
73+
74+
#[test]
75+
fn deserialize_works() {
76+
let e: Result<NonEmptyString, _> = serde_json::from_value(json!("abc"));
77+
78+
let expected = NonEmptyString("abc".to_owned());
79+
80+
assert_matches!(e, Ok(v) if v == expected)
81+
}
82+
83+
#[test]
84+
fn deserialize_empty_fails() {
85+
let e: Result<NonEmptyString, _> = serde_json::from_value(json!(""));
86+
87+
assert!(e.is_err());
88+
// assert_matches!(e, Ok(expected))
89+
}
90+
}

0 commit comments

Comments
 (0)