Skip to content

Commit 98f5828

Browse files
committed
Added CHANGELOG and support for deserialization using serde.
1 parent affc715 commit 98f5828

File tree

4 files changed

+151
-9
lines changed

4 files changed

+151
-9
lines changed

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 <midaslamb@gmail.com>"]
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: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use std::fmt;
2+
3+
use serde::de::{self, Unexpected, Visitor};
4+
5+
use crate::NonEmptyString;
6+
7+
struct NonEmptyStringVisitor;
8+
9+
impl<'de> de::Deserialize<'de> for NonEmptyString {
10+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
11+
where
12+
D: serde::Deserializer<'de>,
13+
{
14+
deserializer.deserialize_string(NonEmptyStringVisitor)
15+
}
16+
}
17+
18+
pub enum DeserializeError {}
19+
20+
type Result<T, E = DeserializeError> = std::result::Result<T, E>;
21+
22+
impl<'de> Visitor<'de> for NonEmptyStringVisitor {
23+
type Value = NonEmptyString;
24+
25+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
26+
formatter.write_str("an integer between -2^31 and 2^31")
27+
}
28+
29+
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
30+
where
31+
E: de::Error,
32+
{
33+
self.visit_string(value.to_owned())
34+
}
35+
36+
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
37+
where
38+
E: de::Error,
39+
{
40+
NonEmptyString::new(value).map_err(|e| de::Error::invalid_value(Unexpected::Str(&e), &self))
41+
}
42+
}
43+
44+
#[cfg(test)]
45+
mod tests {
46+
use super::*;
47+
use crate::*;
48+
use assert_matches::assert_matches;
49+
use serde_json::json;
50+
51+
#[test]
52+
fn deserialize_works() {
53+
let e: Result<NonEmptyString, _> = serde_json::from_value(json!("abc"));
54+
55+
let expected = NonEmptyString("abc".to_owned());
56+
57+
assert_matches!(e, Ok(v) if v == expected)
58+
}
59+
60+
#[test]
61+
fn deserialize_empty_fails() {
62+
let e: Result<NonEmptyString, _> = serde_json::from_value(json!(""));
63+
64+
assert!(e.is_err());
65+
// assert_matches!(e, Ok(expected))
66+
}
67+
}

0 commit comments

Comments
 (0)