1
0
Fork 0
mirror of https://github.com/kotovalexarian/jsonapis.rs.git synced 2025-04-21 17:52:45 -04:00

Add existing code

This commit is contained in:
Alex Kotov 2021-09-10 14:18:05 +05:00
commit 5ef22483f0
Signed by: kotovalexarian
GPG key ID: 553C0EBBEB5D5F08
23 changed files with 2464 additions and 0 deletions

96
src/builders/data.rs Normal file
View file

@ -0,0 +1,96 @@
use super::*;
pub enum DataBuilder {
Single(ResourceBuilder),
Multiple(Vec<ResourceBuilder>),
}
impl Builder for DataBuilder {
type Entity = Data;
fn finish(self) -> Result<Self::Entity, ()> {
Ok(match self {
Self::Single(resource) => Data::Single(resource.finish()?),
Self::Multiple(resources) => Data::Multiple({
let mut new_resources = vec![];
for resource in resources {
new_resources.push(resource.finish()?);
}
new_resources
}),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single() {
assert_eq!(
DataBuilder::Single(ResourceBuilder::new("qwerties")).unwrap(),
Data::Single(Resource {
type_: "qwerties".into(),
id: None,
meta: None,
links: None,
attributes: None,
relationships: None,
}),
);
}
#[test]
fn multiple_zero() {
assert_eq!(
DataBuilder::Multiple(vec![]).unwrap(),
Data::Multiple(vec![]),
);
}
#[test]
fn multiple_one() {
assert_eq!(
DataBuilder::Multiple(vec![ResourceBuilder::new("qwerties")])
.unwrap(),
Data::Multiple(vec![Resource {
type_: "qwerties".into(),
id: None,
meta: None,
links: None,
attributes: None,
relationships: None,
}]),
);
}
#[test]
fn multiple_two() {
assert_eq!(
DataBuilder::Multiple(vec![
ResourceBuilder::new("qwerties"),
ResourceBuilder::new("foobars"),
])
.unwrap(),
Data::Multiple(vec![
Resource {
type_: "qwerties".into(),
id: None,
meta: None,
links: None,
attributes: None,
relationships: None,
},
Resource {
type_: "foobars".into(),
id: None,
meta: None,
links: None,
attributes: None,
relationships: None,
},
]),
);
}
}

231
src/builders/document.rs Normal file
View file

@ -0,0 +1,231 @@
use super::*;
pub struct DocumentBuilder {
jsonapi: Option<JsonApiBuilder>,
meta: Option<MetaOrAttrsBuilder>,
links: Option<LinksBuilder>,
data: Option<DataBuilder>,
}
impl Default for DocumentBuilder {
fn default() -> Self {
Self {
jsonapi: None,
meta: None,
links: None,
data: None,
}
}
}
impl Builder for DocumentBuilder {
type Entity = Document;
fn finish(self) -> Result<Self::Entity, ()> {
Ok(Self::Entity {
jsonapi: match self.jsonapi {
None => None,
Some(jsonapi) => Some(jsonapi.finish()?),
},
meta: match self.meta {
None => None,
Some(meta) => Some(meta.finish()?),
},
links: match self.links {
None => None,
Some(links) => Some(links.finish()?),
},
data: match self.data {
None => None,
Some(data) => Some(data.finish()?),
},
})
}
}
impl DocumentBuilder {
pub fn jsonapi(self, jsonapi: JsonApiBuilder) -> Self {
Self {
jsonapi: Some(jsonapi),
..self
}
}
pub fn meta(self, meta: MetaOrAttrsBuilder) -> Self {
Self {
meta: Some(meta),
..self
}
}
pub fn links(self, links: LinksBuilder) -> Self {
Self {
links: Some(links),
..self
}
}
pub fn data(self, data: DataBuilder) -> Self {
Self {
data: Some(data),
..self
}
}
pub fn data_single(self, resource: ResourceBuilder) -> Self {
self.data(DataBuilder::Single(resource))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn meta() -> MetaOrAttrs {
let mut meta = MetaOrAttrs::new();
meta.insert("foo".into(), 123.into());
meta.insert("bar".into(), "qwe".into());
meta
}
#[test]
fn empty() {
assert_eq!(
DocumentBuilder::default().unwrap(),
Document {
jsonapi: None,
meta: None,
links: None,
data: None,
},
);
}
#[test]
fn full() {
assert_eq!(
DocumentBuilder::default()
.jsonapi(JsonApiBuilder::default().version(Version::new(456)))
.meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
)
.data(DataBuilder::Single(ResourceBuilder::new("qwerties")))
.unwrap(),
Document {
jsonapi: Some(JsonApi {
meta: None,
version: Some(Version::new(456)),
}),
meta: Some(meta()),
links: None,
data: Some(Data::Single(Resource {
type_: "qwerties".into(),
id: None,
meta: None,
links: None,
attributes: None,
relationships: None,
})),
},
);
}
#[test]
fn with_jsonapi() {
assert_eq!(
DocumentBuilder::default()
.jsonapi(JsonApiBuilder::default().version(Version::new(456)))
.unwrap(),
Document {
jsonapi: Some(JsonApi {
meta: None,
version: Some(Version::new(456)),
}),
meta: None,
links: None,
data: None,
},
);
}
#[test]
fn with_meta() {
assert_eq!(
DocumentBuilder::default()
.meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
)
.unwrap(),
Document {
jsonapi: None,
meta: Some(meta()),
links: None,
data: None,
},
);
}
#[test]
fn with_links() {
assert_eq!(
DocumentBuilder::default()
.links(
LinksBuilder::default()
.self_(LinkBuilder::new("http://self.com"))
.prev(
LinkBuilder::new("http://prev.com").meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
),
),
)
.unwrap(),
Document {
jsonapi: None,
meta: None,
links: Some(Links {
other: HashMap::new(),
self_: Some(Link::String("http://self.com".into())),
related: None,
first: None,
last: None,
prev: Some(Link::Object(LinkObject {
href: "http://prev.com".into(),
meta: Some(meta()),
})),
next: None,
}),
data: None,
},
);
}
#[test]
fn with_data() {
assert_eq!(
DocumentBuilder::default()
.data(DataBuilder::Multiple(vec![ResourceBuilder::new(
"qwerties"
)]))
.unwrap(),
Document {
jsonapi: None,
meta: None,
links: None,
data: Some(Data::Multiple(vec![Resource {
type_: "qwerties".into(),
id: None,
meta: None,
links: None,
attributes: None,
relationships: None,
}])),
},
);
}
}

116
src/builders/jsonapi.rs Normal file
View file

@ -0,0 +1,116 @@
use super::*;
pub struct JsonApiBuilder {
version: Option<Version>,
meta: Option<MetaOrAttrsBuilder>,
}
impl Default for JsonApiBuilder {
fn default() -> Self {
Self {
version: None,
meta: None,
}
}
}
impl Builder for JsonApiBuilder {
type Entity = JsonApi;
fn finish(self) -> Result<Self::Entity, ()> {
Ok(Self::Entity {
version: self.version,
meta: match self.meta {
None => None,
Some(meta) => Some(meta.finish()?),
},
})
}
}
impl JsonApiBuilder {
pub fn version(self, version: Version) -> Self {
Self {
version: Some(version),
..self
}
}
pub fn meta(self, meta: MetaOrAttrsBuilder) -> Self {
Self {
meta: Some(meta),
..self
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn meta() -> MetaOrAttrs {
let mut meta = MetaOrAttrs::new();
meta.insert("foo".into(), 123.into());
meta.insert("bar".into(), "qwe".into());
meta
}
#[test]
fn empty() {
assert_eq!(
JsonApiBuilder::default().unwrap(),
JsonApi {
version: None,
meta: None,
},
);
}
#[test]
fn full() {
assert_eq!(
JsonApiBuilder::default()
.version(Version::new(456))
.meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
)
.unwrap(),
JsonApi {
version: Some(Version::new(456)),
meta: Some(meta()),
},
);
}
#[test]
fn with_version() {
assert_eq!(
JsonApiBuilder::default()
.version(Version::new(456))
.unwrap(),
JsonApi {
version: Some(Version::new(456)),
meta: None,
},
);
}
#[test]
fn with_meta() {
assert_eq!(
JsonApiBuilder::default()
.meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
)
.unwrap(),
JsonApi {
version: None,
meta: Some(meta()),
},
);
}
}

75
src/builders/link.rs Normal file
View file

@ -0,0 +1,75 @@
use super::*;
pub struct LinkBuilder {
href: String,
meta: Option<MetaOrAttrsBuilder>,
}
impl LinkBuilder {
pub fn new(href: &str) -> Self {
Self {
href: href.into(),
meta: None,
}
}
}
impl Builder for LinkBuilder {
type Entity = Link;
fn finish(self) -> Result<Self::Entity, ()> {
Ok(match self.meta {
None => Link::String(self.href),
Some(meta) => Link::Object(LinkObject {
href: self.href,
meta: Some(meta.finish()?),
}),
})
}
}
impl LinkBuilder {
pub fn meta(self, meta: MetaOrAttrsBuilder) -> Self {
Self {
meta: Some(meta),
..self
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn meta() -> MetaOrAttrs {
let mut meta = MetaOrAttrs::new();
meta.insert("foo".into(), 123.into());
meta.insert("bar".into(), "qwe".into());
meta
}
#[test]
fn empty() {
assert_eq!(
LinkBuilder::new("http://example.com").unwrap(),
Link::String("http://example.com".into()),
);
}
#[test]
fn full() {
assert_eq!(
LinkBuilder::new("http://example.com")
.meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
)
.unwrap(),
Link::Object(LinkObject {
href: "http://example.com".into(),
meta: Some(meta()),
}),
);
}
}

282
src/builders/links.rs Normal file
View file

@ -0,0 +1,282 @@
use super::*;
pub struct LinksBuilder {
pub other: HashMap<String, LinkBuilder>,
pub self_: Option<LinkBuilder>,
pub related: Option<LinkBuilder>,
pub first: Option<LinkBuilder>,
pub last: Option<LinkBuilder>,
pub prev: Option<LinkBuilder>,
pub next: Option<LinkBuilder>,
}
impl Default for LinksBuilder {
fn default() -> Self {
Self {
other: HashMap::new(),
self_: None,
related: None,
first: None,
last: None,
prev: None,
next: None,
}
}
}
impl Builder for LinksBuilder {
type Entity = Links;
fn finish(self) -> Result<Self::Entity, ()> {
let mut other = HashMap::new();
for (key, value) in self.other {
other.insert(key, value.finish()?);
}
Ok(Self::Entity {
other,
self_: match self.self_ {
None => None,
Some(self_) => Some(self_.finish()?),
},
related: match self.related {
None => None,
Some(related) => Some(related.finish()?),
},
first: match self.first {
None => None,
Some(first) => Some(first.finish()?),
},
last: match self.last {
None => None,
Some(last) => Some(last.finish()?),
},
prev: match self.prev {
None => None,
Some(prev) => Some(prev.finish()?),
},
next: match self.next {
None => None,
Some(next) => Some(next.finish()?),
},
})
}
}
impl LinksBuilder {
pub fn self_(self, self_: LinkBuilder) -> Self {
Self {
self_: Some(self_),
..self
}
}
pub fn related(self, related: LinkBuilder) -> Self {
Self {
related: Some(related),
..self
}
}
pub fn first(self, first: LinkBuilder) -> Self {
Self {
first: Some(first),
..self
}
}
pub fn last(self, last: LinkBuilder) -> Self {
Self {
last: Some(last),
..self
}
}
pub fn prev(self, prev: LinkBuilder) -> Self {
Self {
prev: Some(prev),
..self
}
}
pub fn next(self, next: LinkBuilder) -> Self {
Self {
next: Some(next),
..self
}
}
pub fn link(self, name: &str, link: LinkBuilder) -> Self {
if name == "self" {
return Self {
self_: Some(link),
..self
};
}
if name == "related" {
return Self {
related: Some(link),
..self
};
}
if name == "first" {
return Self {
first: Some(link),
..self
};
}
if name == "last" {
return Self {
last: Some(link),
..self
};
}
if name == "prev" {
return Self {
prev: Some(link),
..self
};
}
if name == "next" {
return Self {
next: Some(link),
..self
};
}
let mut other = self.other;
other.insert(name.into(), link);
Self { other, ..self }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn meta() -> MetaOrAttrs {
let mut meta = MetaOrAttrs::new();
meta.insert("foo".into(), 123.into());
meta.insert("bar".into(), "qwe".into());
meta
}
#[test]
fn empty() {
assert_eq!(
LinksBuilder::default().unwrap(),
Links {
other: HashMap::new(),
self_: None,
related: None,
first: None,
last: None,
prev: None,
next: None,
},
);
}
#[test]
fn full() {
assert_eq!(
LinksBuilder::default()
.self_(LinkBuilder::new("http://self.com"))
.related(LinkBuilder::new("http://related.com"))
.first(
LinkBuilder::new("http://first.com").meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
),
)
.last(LinkBuilder::new("http://last.com"))
.prev(LinkBuilder::new("http://prev.com"))
.next(LinkBuilder::new("http://next.com"))
.link("foo", LinkBuilder::new("http://foo.com"))
.link(
"bar",
LinkBuilder::new("http://bar.com").meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
),
)
.unwrap(),
Links {
other: {
let mut other = HashMap::new();
other.insert(
"foo".into(),
Link::String("http://foo.com".into()),
);
other.insert(
"bar".into(),
Link::Object(LinkObject {
href: "http://bar.com".into(),
meta: Some(meta()),
}),
);
other
},
self_: Some(Link::String("http://self.com".into())),
related: Some(Link::String("http://related.com".into())),
first: Some(Link::Object(LinkObject {
href: "http://first.com".into(),
meta: Some(meta()),
})),
last: Some(Link::String("http://last.com".into())),
prev: Some(Link::String("http://prev.com".into())),
next: Some(Link::String("http://next.com".into())),
},
);
}
#[test]
fn full_common_with_link() {
assert_eq!(
LinksBuilder::default()
.link("self", LinkBuilder::new("http://self.com"))
.link("related", LinkBuilder::new("http://related.com"))
.link("first", LinkBuilder::new("http://first.com"))
.link(
"last",
LinkBuilder::new("http://last.com").meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
),
)
.link("prev", LinkBuilder::new("http://prev.com"))
.link("next", LinkBuilder::new("http://next.com"))
.link("foo", LinkBuilder::new("http://foo.com"))
.unwrap(),
Links {
other: {
let mut other = HashMap::new();
other.insert(
"foo".into(),
Link::String("http://foo.com".into()),
);
other
},
self_: Some(Link::String("http://self.com".into())),
related: Some(Link::String("http://related.com".into())),
first: Some(Link::String("http://first.com".into())),
last: Some(Link::Object(LinkObject {
href: "http://last.com".into(),
meta: Some(meta()),
})),
prev: Some(Link::String("http://prev.com".into())),
next: Some(Link::String("http://next.com".into())),
},
);
}
}

View file

@ -0,0 +1,94 @@
use super::*;
pub struct MetaOrAttrsBuilder(MetaOrAttrs);
impl Default for MetaOrAttrsBuilder {
fn default() -> Self {
Self(MetaOrAttrs::new())
}
}
impl Builder for MetaOrAttrsBuilder {
type Entity = MetaOrAttrs;
fn finish(self) -> Result<Self::Entity, ()> {
let mut meta_or_attrs = MetaOrAttrs::new();
for (key, value) in self.0 {
meta_or_attrs.insert(key, value);
}
Ok(meta_or_attrs)
}
}
impl MetaOrAttrsBuilder {
pub fn item<V>(self, name: &str, value: V) -> Self
where
V: Into<Value>,
{
let mut meta_or_attrs = self.0;
meta_or_attrs.insert(name.into(), value.into());
Self(meta_or_attrs)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty() {
assert_eq!(MetaOrAttrsBuilder::default().unwrap(), MetaOrAttrs::new(),);
}
#[test]
fn full() {
assert_eq!(
MetaOrAttrsBuilder::default()
.item("false", false)
.item("true", true)
.item("int", 123)
.item("float", 3.1415926536)
.item("str", "Hello, World!")
.item("array", vec![1, 2, 3])
.item("object", {
let mut object = serde_json::Map::new();
object.insert("foo".into(), Value::Number(123.into()));
Value::Object(object)
})
.unwrap(),
{
let mut meta_or_attrs = MetaOrAttrs::new();
meta_or_attrs.insert("false".into(), Value::Bool(false));
meta_or_attrs.insert("true".into(), Value::Bool(true));
meta_or_attrs.insert("int".into(), Value::Number(123.into()));
meta_or_attrs.insert(
"float".into(),
Value::Number(
serde_json::Number::from_f64(3.1415926536).unwrap(),
),
);
meta_or_attrs.insert(
"str".into(),
Value::String("Hello, World!".into()),
);
meta_or_attrs.insert(
"array".into(),
vec![
Value::Number(1.into()),
Value::Number(2.into()),
Value::Number(3.into()),
]
.into(),
);
meta_or_attrs.insert("object".into(), {
let mut object = serde_json::Map::new();
object.insert("foo".into(), Value::Number(123.into()));
Value::Object(object)
});
meta_or_attrs
}
);
}
}

35
src/builders/mod.rs Normal file
View file

@ -0,0 +1,35 @@
mod data;
mod document;
mod jsonapi;
mod link;
mod links;
mod meta_or_attrs;
mod relationship;
mod relationships;
mod resource;
pub use data::DataBuilder;
pub use document::DocumentBuilder;
pub use jsonapi::JsonApiBuilder;
pub use link::LinkBuilder;
pub use links::LinksBuilder;
pub use meta_or_attrs::MetaOrAttrsBuilder;
pub use relationship::RelationshipBuilder;
pub use relationships::RelationshipsBuilder;
pub use resource::ResourceBuilder;
use super::entities::*;
use std::collections::HashMap;
use serde_json::Value;
pub trait Builder: Sized {
type Entity: Entity;
fn finish(self) -> Result<Self::Entity, ()>;
fn unwrap(self) -> Self::Entity {
self.finish().unwrap()
}
}

View file

@ -0,0 +1,137 @@
use super::*;
pub struct RelationshipBuilder {
meta: Option<MetaOrAttrsBuilder>,
links: Option<LinksBuilder>,
data: Option<DataBuilder>,
}
impl Default for RelationshipBuilder {
fn default() -> Self {
Self {
meta: None,
links: None,
data: None,
}
}
}
impl Builder for RelationshipBuilder {
type Entity = Relationship;
fn finish(self) -> Result<Self::Entity, ()> {
Ok(Self::Entity {
meta: match self.meta {
None => None,
Some(meta) => Some(meta.finish()?),
},
links: match self.links {
None => None,
Some(links) => Some(links.finish()?),
},
data: match self.data {
None => None,
Some(data) => Some(data.finish()?),
},
})
}
}
impl RelationshipBuilder {
pub fn meta(self, meta: MetaOrAttrsBuilder) -> Self {
Self {
meta: Some(meta),
..self
}
}
pub fn links(self, links: LinksBuilder) -> Self {
Self {
links: Some(links),
..self
}
}
pub fn data(self, data: DataBuilder) -> Self {
Self {
data: Some(data),
..self
}
}
pub fn data_single(self, resource: ResourceBuilder) -> Self {
self.data(DataBuilder::Single(resource))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn meta() -> MetaOrAttrs {
let mut meta = MetaOrAttrs::new();
meta.insert("foo".into(), 123.into());
meta.insert("bar".into(), "qwe".into());
meta
}
#[test]
fn empty() {
assert_eq!(
RelationshipBuilder::default().unwrap(),
Relationship {
meta: None,
links: None,
data: None,
},
);
}
#[test]
fn full() {
assert_eq!(
RelationshipBuilder::default()
.meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
)
.links(
LinksBuilder::default()
.self_(LinkBuilder::new("http://self.com"))
.prev(
LinkBuilder::new("http://prev.com").meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
),
),
)
.data(DataBuilder::Single(ResourceBuilder::new("qwerties")))
.unwrap(),
Relationship {
meta: Some(meta()),
links: Some(Links {
other: HashMap::new(),
self_: Some(Link::String("http://self.com".into())),
related: None,
first: None,
last: None,
prev: Some(Link::Object(LinkObject {
href: "http://prev.com".into(),
meta: Some(meta()),
})),
next: None,
}),
data: Some(Data::Single(Resource {
type_: "qwerties".into(),
id: None,
meta: None,
links: None,
attributes: None,
relationships: None,
})),
},
);
}
}

View file

@ -0,0 +1,109 @@
use super::*;
pub struct RelationshipsBuilder(HashMap<String, RelationshipBuilder>);
impl Default for RelationshipsBuilder {
fn default() -> Self {
Self(HashMap::new())
}
}
impl Builder for RelationshipsBuilder {
type Entity = Relationships;
fn finish(self) -> Result<Self::Entity, ()> {
let mut relationships = Relationships::new();
for (name, relationship) in self.0 {
relationships.insert(name, relationship.finish()?);
}
Ok(relationships)
}
}
impl RelationshipsBuilder {
pub fn rel(self, name: &str, relationship: RelationshipBuilder) -> Self {
let mut relationships = self.0;
relationships.insert(name.into(), relationship);
Self(relationships)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn meta() -> MetaOrAttrs {
let mut meta = MetaOrAttrs::new();
meta.insert("foo".into(), 123.into());
meta.insert("bar".into(), "qwe".into());
meta
}
#[test]
fn empty() {
assert_eq!(
RelationshipsBuilder::default().unwrap(),
Relationships::new(),
);
}
#[test]
fn full() {
assert_eq!(
RelationshipsBuilder::default()
.rel("foo", RelationshipBuilder::default())
.rel(
"bar",
RelationshipBuilder::default().meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
),
)
.rel(
"car",
RelationshipBuilder::default().data(DataBuilder::Single(
ResourceBuilder::new("qwerties")
)),
)
.unwrap(),
{
let mut relationships = Relationships::new();
relationships.insert(
"foo".into(),
Relationship {
meta: None,
links: None,
data: None,
},
);
relationships.insert(
"bar".into(),
Relationship {
meta: Some(meta()),
links: None,
data: None,
},
);
relationships.insert(
"car".into(),
Relationship {
meta: None,
links: None,
data: Some(Data::Single(Resource {
type_: "qwerties".into(),
id: None,
meta: None,
links: None,
attributes: None,
relationships: None,
})),
},
);
relationships
},
);
}
}

292
src/builders/resource.rs Normal file
View file

@ -0,0 +1,292 @@
use super::*;
pub struct ResourceBuilder {
type_: String,
id: Option<String>,
meta: Option<MetaOrAttrsBuilder>,
links: Option<LinksBuilder>,
attributes: Option<MetaOrAttrsBuilder>,
relationships: Option<RelationshipsBuilder>,
}
impl ResourceBuilder {
pub fn new(type_: &str) -> Self {
Self {
type_: type_.into(),
id: None,
meta: None,
links: None,
attributes: None,
relationships: None,
}
}
pub fn new_with_id<I>(type_: &str, id: I) -> Self
where
I: ToString,
{
Self {
type_: type_.into(),
id: Some(id.to_string()),
meta: None,
links: None,
attributes: None,
relationships: None,
}
}
}
impl Builder for ResourceBuilder {
type Entity = Resource;
fn finish(self) -> Result<Self::Entity, ()> {
Ok(Self::Entity {
type_: self.type_,
id: self.id,
meta: match self.meta {
None => None,
Some(meta) => Some(meta.finish()?),
},
links: match self.links {
None => None,
Some(links) => Some(links.finish()?),
},
attributes: match self.attributes {
None => None,
Some(attributes) => Some(attributes.finish()?),
},
relationships: match self.relationships {
None => None,
Some(relationships) => Some(relationships.finish()?),
},
})
}
}
impl ResourceBuilder {
pub fn id(self, id: &str) -> Self {
Self {
id: Some(id.into()),
..self
}
}
pub fn meta(self, meta: MetaOrAttrsBuilder) -> Self {
Self {
meta: Some(meta),
..self
}
}
pub fn links(self, links: LinksBuilder) -> Self {
Self {
links: Some(links),
..self
}
}
pub fn attributes(self, attributes: MetaOrAttrsBuilder) -> Self {
Self {
attributes: Some(attributes),
..self
}
}
pub fn relationships(self, relationships: RelationshipsBuilder) -> Self {
Self {
relationships: Some(relationships),
..self
}
}
pub fn attr<V>(self, name: &str, attribute: V) -> Self
where
V: Into<Value>,
{
let attributes = self
.attributes
.unwrap_or(MetaOrAttrsBuilder::default())
.item(name, attribute);
Self {
attributes: Some(attributes),
..self
}
}
pub fn rel(self, name: &str, relationship: RelationshipBuilder) -> Self {
let relationships = self
.relationships
.unwrap_or(RelationshipsBuilder::default())
.rel(name, relationship);
Self {
relationships: Some(relationships),
..self
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn meta() -> MetaOrAttrs {
let mut meta = MetaOrAttrs::new();
meta.insert("foo".into(), 123.into());
meta.insert("bar".into(), "qwe".into());
meta
}
#[test]
fn empty() {
assert_eq!(
ResourceBuilder::new("qwerties").unwrap(),
Resource {
type_: "qwerties".into(),
id: None,
meta: None,
links: None,
attributes: None,
relationships: None,
},
);
}
#[test]
fn empty_with_id() {
assert_eq!(
ResourceBuilder::new_with_id("qwerties", 123).unwrap(),
Resource {
type_: "qwerties".into(),
id: Some("123".into()),
meta: None,
links: None,
attributes: None,
relationships: None,
},
);
}
#[test]
fn full() {
assert_eq!(
ResourceBuilder::new("qwerties")
.id("123")
.meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
)
.links(
LinksBuilder::default()
.self_(LinkBuilder::new("http://self.com"))
.next(
LinkBuilder::new("http://next.com").meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
),
),
)
.attributes(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
)
.relationships(
RelationshipsBuilder::default()
.rel("foo", RelationshipBuilder::default())
)
.unwrap(),
Resource {
type_: "qwerties".into(),
id: Some("123".into()),
meta: Some(meta()),
links: Some(Links {
other: HashMap::new(),
self_: Some(Link::String("http://self.com".into())),
related: None,
first: None,
last: None,
prev: None,
next: Some(Link::Object(LinkObject {
href: "http://next.com".into(),
meta: Some(meta()),
})),
}),
attributes: Some(meta()),
relationships: Some({
let mut relationships = Relationships::new();
relationships.insert(
"foo".into(),
Relationship {
meta: None,
links: None,
data: None,
},
);
relationships
}),
},
);
}
#[test]
fn full_delegators() {
assert_eq!(
ResourceBuilder::new("qwerties")
.id("123")
.meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
)
.links(
LinksBuilder::default()
.self_(LinkBuilder::new("http://self.com"))
.next(
LinkBuilder::new("http://next.com").meta(
MetaOrAttrsBuilder::default()
.item("foo", 123)
.item("bar", "qwe"),
),
),
)
.attr("foo", 123)
.attr("bar", "qwe")
.rel("foo", RelationshipBuilder::default())
.unwrap(),
Resource {
type_: "qwerties".into(),
id: Some("123".into()),
meta: Some(meta()),
links: Some(Links {
other: HashMap::new(),
self_: Some(Link::String("http://self.com".into())),
related: None,
first: None,
last: None,
prev: None,
next: Some(Link::Object(LinkObject {
href: "http://next.com".into(),
meta: Some(meta()),
})),
}),
attributes: Some(meta()),
relationships: Some({
let mut relationships = Relationships::new();
relationships.insert(
"foo".into(),
Relationship {
meta: None,
links: None,
data: None,
},
);
relationships
}),
},
);
}
}

58
src/entities/data.rs Normal file
View file

@ -0,0 +1,58 @@
use super::*;
impl Entity for Data {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Data {
Single(Resource),
Multiple(Vec<Resource>),
}
impl Serialize for Data {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Single(single) => single.serialize(serializer),
Self::Multiple(multiple) => multiple.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for Data {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct MyVisitor;
impl<'de> Visitor<'de> for MyVisitor {
type Value = Data;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("JSON API data")
}
fn visit_map<A>(self, value: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
Ok(Data::Single(Deserialize::deserialize(
serde::de::value::MapAccessDeserializer::new(value),
)?))
}
fn visit_seq<A>(self, value: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
Ok(Data::Multiple(Deserialize::deserialize(
serde::de::value::SeqAccessDeserializer::new(value),
)?))
}
}
deserializer.deserialize_any(MyVisitor)
}
}

11
src/entities/document.rs Normal file
View file

@ -0,0 +1,11 @@
use super::*;
impl Entity for Document {}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Document {
pub jsonapi: Option<JsonApi>,
pub meta: Option<MetaOrAttrs>,
pub links: Option<Links>,
pub data: Option<Data>,
}

9
src/entities/jsonapi.rs Normal file
View file

@ -0,0 +1,9 @@
use super::*;
impl Entity for JsonApi {}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct JsonApi {
pub meta: Option<MetaOrAttrs>,
pub version: Option<Version>,
}

56
src/entities/link.rs Normal file
View file

@ -0,0 +1,56 @@
use super::*;
impl Entity for Link {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Link {
String(String),
Object(LinkObject),
}
impl Serialize for Link {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::String(string) => serializer.serialize_str(string),
Self::Object(object) => object.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for Link {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct MyVisitor;
impl<'de> Visitor<'de> for MyVisitor {
type Value = Link;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("JSON API link")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Link::String(value.to_string()))
}
fn visit_map<A>(self, value: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
Ok(Link::Object(Deserialize::deserialize(
serde::de::value::MapAccessDeserializer::new(value),
)?))
}
}
deserializer.deserialize_any(MyVisitor)
}
}

View file

@ -0,0 +1,9 @@
use super::*;
impl Entity for LinkObject {}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct LinkObject {
pub href: String,
pub meta: Option<MetaOrAttrs>,
}

101
src/entities/links.rs Normal file
View file

@ -0,0 +1,101 @@
use super::*;
impl Entity for Links {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Links {
pub other: HashMap<String, Link>,
pub self_: Option<Link>,
pub related: Option<Link>,
pub first: Option<Link>,
pub last: Option<Link>,
pub prev: Option<Link>,
pub next: Option<Link>,
}
impl Serialize for Links {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut all: HashMap<String, Option<Link>> = HashMap::new();
for (key, value) in &self.other {
all.insert(key.into(), Some(value.clone()));
}
all.insert("self".into(), self.self_.clone());
all.insert("related".into(), self.related.clone());
all.insert("first".into(), self.first.clone());
all.insert("last".into(), self.last.clone());
all.insert("prev".into(), self.prev.clone());
all.insert("next".into(), self.next.clone());
let mut map = serializer.serialize_map(Some(all.len()))?;
for (key, value) in all {
map.serialize_entry(&key, &value)?;
}
map.end()
}
}
impl<'de> Deserialize<'de> for Links {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct MyVisitor;
impl<'de> Visitor<'de> for MyVisitor {
type Value = Links;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("JSON API links")
}
fn visit_map<A>(self, value: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
match Deserialize::deserialize(
serde::de::value::MapAccessDeserializer::new(value),
) {
Err(err) => Err(err),
Ok(all) => {
let mut all: HashMap<String, Option<Link>> = all;
let self_: Option<Option<Link>> = all.remove("self");
let related: Option<Option<Link>> =
all.remove("related");
let first: Option<Option<Link>> = all.remove("first");
let last: Option<Option<Link>> = all.remove("last");
let prev: Option<Option<Link>> = all.remove("prev");
let next: Option<Option<Link>> = all.remove("next");
let mut other: HashMap<String, Link> = HashMap::new();
for (key, value) in all {
if let Some(value) = value {
other.insert(key, value);
}
}
Ok(Self::Value {
other,
self_: self_.unwrap_or(None),
related: related.unwrap_or(None),
first: first.unwrap_or(None),
last: last.unwrap_or(None),
prev: prev.unwrap_or(None),
next: next.unwrap_or(None),
})
}
}
}
}
deserializer.deserialize_map(MyVisitor)
}
}

View file

@ -0,0 +1,5 @@
use super::*;
impl Entity for MetaOrAttrs {}
pub type MetaOrAttrs = HashMap<String, Value>;

511
src/entities/mod.rs Normal file
View file

@ -0,0 +1,511 @@
mod data;
mod document;
mod jsonapi;
mod link;
mod link_object;
mod links;
mod meta_or_attrs;
mod relationship;
mod relationships;
mod resource;
mod version;
pub use data::Data;
pub use document::Document;
pub use jsonapi::JsonApi;
pub use link::Link;
pub use link_object::LinkObject;
pub use links::Links;
pub use meta_or_attrs::MetaOrAttrs;
pub use relationship::Relationship;
pub use relationships::Relationships;
pub use resource::Resource;
pub use version::Version;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::fmt::{self, Debug, Display, Formatter};
use std::str::FromStr;
use serde::{
de::Visitor,
ser::{SerializeMap, Serializer},
Deserialize, Deserializer, Serialize,
};
use serde_json::Value;
pub trait Entity {}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn expected_meta_or_attrs() -> MetaOrAttrs {
let mut expected_meta_or_attrs: MetaOrAttrs = MetaOrAttrs::new();
expected_meta_or_attrs.insert("foo".into(), Value::Number(123.into()));
expected_meta_or_attrs
.insert("bar".into(), Value::String("qwe".into()));
expected_meta_or_attrs
}
fn expected_meta_or_attrs_value() -> Value {
json!({
"foo": json!(123),
"bar": json!("qwe"),
})
}
fn expected_links() -> Links {
let mut expected_links: Links = Links {
other: HashMap::new(),
self_: Some(Link::String("http://example.com".into())),
related: None,
first: None,
last: None,
prev: None,
next: Some(Link::Object(LinkObject {
href: "http://example.com".into(),
meta: Some(expected_meta_or_attrs()),
})),
};
expected_links
.other
.insert("foo".into(), Link::String("http://foo.com".into()));
expected_links.other.insert(
"bar".into(),
Link::Object(LinkObject {
href: "http://bar.com".into(),
meta: Some(expected_meta_or_attrs()),
}),
);
expected_links
}
fn expected_links_value() -> Value {
json!({
"self": json!("http://example.com"),
"related": json!(null),
"first": json!(null),
"last": json!(null),
"prev": json!(null),
"next": json!({
"href": json!("http://example.com"),
"meta": expected_meta_or_attrs_value(),
}),
"foo": json!("http://foo.com"),
"bar": json!({
"href": json!("http://bar.com"),
"meta": expected_meta_or_attrs_value(),
}),
})
}
fn expected_relationships() -> Relationships {
let mut expected_relationships: Relationships = Relationships::new();
expected_relationships.insert(
"car".into(),
Relationship {
meta: None,
links: None,
data: None,
},
);
expected_relationships.insert(
"cdr".into(),
Relationship {
meta: Some(expected_meta_or_attrs()),
links: Some(expected_links()),
data: Some(Data::Single(Resource {
type_: "qwerties".into(),
id: Some("123".into()),
meta: Some(expected_meta_or_attrs()),
links: Some(expected_links()),
attributes: Some(expected_meta_or_attrs()),
relationships: None,
})),
},
);
expected_relationships
}
fn expected_relationships_value() -> Value {
json!({
"car": json!({
"meta": json!(null),
"links": json!(null),
"data": json!(null),
}),
"cdr": json!({
"meta": expected_meta_or_attrs_value(),
"links": expected_links_value(),
"data": json!({
"type": json!("qwerties"),
"id": json!("123"),
"meta": expected_meta_or_attrs_value(),
"links": expected_links_value(),
"attributes": expected_meta_or_attrs_value(),
"relationships": json!(null),
}),
}),
})
}
#[test]
fn serialize_and_deserialize() {
let document = Document {
jsonapi: Some(JsonApi {
version: Some(Version::new(0)),
meta: Some(expected_meta_or_attrs()),
}),
meta: Some(expected_meta_or_attrs()),
links: Some(expected_links()),
data: Some(Data::Multiple(vec![Resource {
type_: "qwerties".into(),
id: Some("123".into()),
meta: Some(expected_meta_or_attrs()),
links: Some(expected_links()),
attributes: Some(expected_meta_or_attrs()),
relationships: Some(expected_relationships()),
}])),
};
let serialized = serde_json::to_string(&document).unwrap();
let deserialized: Document = serde_json::from_str(&serialized).unwrap();
assert_eq!(document, deserialized);
}
mod deserialize {
use super::*;
#[test]
fn default() {
let expected_document = Document {
jsonapi: Some(JsonApi {
version: Some(Version::new(0)),
meta: Some(expected_meta_or_attrs()),
}),
meta: Some(expected_meta_or_attrs()),
links: Some(expected_links()),
data: None,
};
let value = json!({
"jsonapi": json!({
"version": json!("1.0"),
"meta": expected_meta_or_attrs_value(),
}),
"meta": expected_meta_or_attrs_value(),
"links": expected_links_value(),
});
let json = serde_json::to_string(&value).unwrap();
let document: Document = serde_json::from_str(&json).unwrap();
assert_eq!(document, expected_document);
}
#[test]
fn data_as_null() {
let expected_document = Document {
jsonapi: None,
meta: None,
links: None,
data: None,
};
let json = "{\"data\": null}";
let document: Document = serde_json::from_str(json).unwrap();
assert_eq!(document, expected_document);
}
#[test]
fn data_as_empty_array() {
let expected_document = Document {
jsonapi: None,
meta: None,
links: None,
data: Some(Data::Multiple(vec![])),
};
let json = "{\"data\": []}";
let document: Document = serde_json::from_str(json).unwrap();
assert_eq!(document, expected_document);
}
#[test]
fn data_as_single_resource() {
let expected_document = Document {
jsonapi: None,
meta: None,
links: None,
data: Some(Data::Single(Resource {
type_: "qwerties".into(),
id: Some("123".into()),
meta: Some(expected_meta_or_attrs()),
links: Some(expected_links()),
attributes: Some(expected_meta_or_attrs()),
relationships: Some(expected_relationships()),
})),
};
let value = json!({
"data": json!({
"type": json!("qwerties"),
"id": json!("123"),
"meta": expected_meta_or_attrs_value(),
"links": expected_links_value(),
"attributes": expected_meta_or_attrs_value(),
"relationships": expected_relationships_value(),
}),
});
let json = serde_json::to_string(&value).unwrap();
let document: Document = serde_json::from_str(&json).unwrap();
assert_eq!(document, expected_document);
}
#[test]
fn data_as_multiple_resources() {
let expected_document = Document {
jsonapi: None,
meta: None,
links: None,
data: Some(Data::Multiple(vec![Resource {
type_: "qwerties".into(),
id: Some("123".into()),
meta: Some(expected_meta_or_attrs()),
links: Some(expected_links()),
attributes: Some(expected_meta_or_attrs()),
relationships: Some(expected_relationships()),
}])),
};
let value = json!({
"data": json!([
json!({
"type": json!("qwerties"),
"id": json!("123"),
"meta": expected_meta_or_attrs_value(),
"links": expected_links_value(),
"attributes": expected_meta_or_attrs_value(),
"relationships": expected_relationships_value(),
}),
]),
});
let json = serde_json::to_string(&value).unwrap();
let document: Document = serde_json::from_str(&json).unwrap();
assert_eq!(document, expected_document);
}
}
mod serialize {
use super::*;
#[test]
fn empty() {
let document = Document {
jsonapi: None,
meta: None,
links: None,
data: None,
};
let json = serde_json::to_string(&document).unwrap();
let value: Value = serde_json::from_str(&json).unwrap();
assert_eq!(
value,
json!({
"jsonapi": json!(null),
"meta": json!(null),
"links": json!(null),
"data": json!(null),
})
);
}
#[test]
fn default() {
let document = Document {
jsonapi: Some(JsonApi {
version: Some(Version::new(0)),
meta: Some(expected_meta_or_attrs()),
}),
meta: Some(expected_meta_or_attrs()),
links: Some(expected_links()),
data: Some(Data::Multiple(vec![Resource {
type_: "qwerties".into(),
id: Some("123".into()),
meta: Some(expected_meta_or_attrs()),
links: Some(expected_links()),
attributes: Some(expected_meta_or_attrs()),
relationships: Some(expected_relationships()),
}])),
};
let json = serde_json::to_string(&document).unwrap();
let value: Value = serde_json::from_str(&json).unwrap();
assert_eq!(
value,
json!({
"jsonapi": json!({
"version": json!("1.0"),
"meta": expected_meta_or_attrs_value(),
}),
"meta": expected_meta_or_attrs_value(),
"links": expected_links_value(),
"data": json!([
json!({
"type": json!("qwerties"),
"id": json!("123"),
"meta": expected_meta_or_attrs_value(),
"links": expected_links_value(),
"attributes": expected_meta_or_attrs_value(),
"relationships": expected_relationships_value(),
}),
]),
})
);
}
#[test]
fn links_empty() {
let links = Links {
other: HashMap::new(),
self_: None,
related: None,
first: None,
last: None,
prev: None,
next: None,
};
let json = serde_json::to_string(&links).unwrap();
let value: Value = serde_json::from_str(&json).unwrap();
assert_eq!(
value,
json!({
"self": json!(null),
"related": json!(null),
"first": json!(null),
"last": json!(null),
"prev": json!(null),
"next": json!(null),
})
);
}
#[test]
fn links_default() {
let links = Links {
other: {
let mut other = HashMap::new();
other.insert(
"foo".into(),
Link::String("http://foo.com".into()),
);
other.insert(
"bar".into(),
Link::Object(LinkObject {
href: "http://bar.com".into(),
meta: None,
}),
);
other
},
self_: Some(Link::String("http://self.com".into())),
related: Some(Link::String("http://related.com".into())),
first: Some(Link::Object(LinkObject {
href: "http://first.com".into(),
meta: None,
})),
last: Some(Link::String("http://last.com".into())),
prev: Some(Link::Object(LinkObject {
href: "http://prev.com".into(),
meta: Some({
let mut meta = HashMap::new();
meta.insert("qwerty".into(), json!(123456));
meta
}),
})),
next: Some(Link::String("http://next.com".into())),
};
let json = serde_json::to_string(&links).unwrap();
let value: Value = serde_json::from_str(&json).unwrap();
assert_eq!(
value,
json!({
"self": json!("http://self.com"),
"related": json!("http://related.com"),
"first": json!({
"href": json!("http://first.com"),
"meta": json!(null),
}),
"last": json!("http://last.com"),
"prev": json!({
"href": json!("http://prev.com"),
"meta": json!({ "qwerty": json!(123456) }),
}),
"next": json!("http://next.com"),
"foo": json!("http://foo.com"),
"bar": json!({
"href": json!("http://bar.com"),
"meta": json!(null),
}),
})
);
}
#[test]
fn resource_empty() {
let resource = Resource {
type_: "qwerties".into(),
id: None,
meta: None,
links: None,
attributes: None,
relationships: None,
};
let json = serde_json::to_string(&resource).unwrap();
let value: Value = serde_json::from_str(&json).unwrap();
assert_eq!(
value,
json!({
"type": json!("qwerties"),
"id": json!(null),
"meta": json!(null),
"links": json!(null),
"attributes": json!(null),
"relationships": json!(null),
})
);
}
}
}

View file

@ -0,0 +1,10 @@
use super::*;
impl Entity for Relationship {}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Relationship {
pub meta: Option<MetaOrAttrs>,
pub links: Option<Links>,
pub data: Option<Data>,
}

View file

@ -0,0 +1,5 @@
use super::*;
impl Entity for Relationships {}
pub type Relationships = HashMap<String, Relationship>;

14
src/entities/resource.rs Normal file
View file

@ -0,0 +1,14 @@
use super::*;
impl Entity for Resource {}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Resource {
#[serde(rename = "type")]
pub type_: String,
pub id: Option<String>,
pub meta: Option<MetaOrAttrs>,
pub links: Option<Links>,
pub attributes: Option<MetaOrAttrs>,
pub relationships: Option<Relationships>,
}

203
src/entities/version.rs Normal file
View file

@ -0,0 +1,203 @@
use super::*;
impl Entity for Version {}
#[derive(Clone, Eq, PartialEq)]
pub struct Version {
full: String,
minor: u64,
}
impl Version {
pub fn new(minor: u64) -> Self {
Self {
full: format!("1.{}", minor),
minor,
}
}
}
impl Debug for Version {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.debug_tuple("Version").field(&self.full).finish()
}
}
impl Display for Version {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.full, formatter)
}
}
impl Default for Version {
fn default() -> Self {
Self {
full: "1.0".into(),
minor: 0,
}
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
self.minor.cmp(&other.minor)
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl FromStr for Version {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
// TODO: make static
let regex = regex::Regex::new(r"^1\.(\d+)$").unwrap();
let captures = regex.captures(s).ok_or(())?;
let minor: u64 = captures[1].parse().or(Err(()))?;
Ok(Self::new(minor))
}
}
impl Serialize for Version {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.full)
}
}
impl<'de> Deserialize<'de> for Version {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct MyVisitor;
impl<'de> Visitor<'de> for MyVisitor {
type Value = Version;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("JSON API version")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
value.parse().map_err(|_| {
serde::de::Error::custom("invalid JSON API version")
})
}
}
deserializer.deserialize_str(MyVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clone() {
assert_eq!(Version::default().clone(), Version::default());
assert_eq!(Version::new(123).clone(), Version::new(123));
}
#[test]
fn debug() {
assert_eq!(format!("{:?}", Version::default()), "Version(\"1.0\")");
assert_eq!(format!("{:?}", Version::new(123)), "Version(\"1.123\")");
}
#[test]
fn display() {
assert_eq!(format!("{}", Version::default()), "1.0");
assert_eq!(format!("{}", Version::new(123)), "1.123");
assert_eq!(format!("{:<5}", Version::default()), "1.0 ");
assert_eq!(format!("{:_>6}", Version::default()), "___1.0");
}
#[test]
fn to_string() {
assert_eq!(Version::default().to_string(), "1.0");
assert_eq!(Version::new(123).to_string(), "1.123");
}
#[test]
fn default() {
let version = Version::default();
assert_eq!(version, Version::new(0));
assert_eq!(version.full, "1.0");
assert_eq!(version.minor, 0);
}
#[test]
fn equality() {
assert_eq!(Version::new(0), Version::new(0));
assert_eq!(Version::new(1), Version::new(1));
assert_eq!(Version::new(2), Version::new(2));
assert_eq!(Version::new(1234), Version::new(1234));
assert_ne!(Version::new(0), Version::new(1));
assert_ne!(Version::new(0), Version::new(2));
assert_ne!(Version::new(0), Version::new(1234));
assert_ne!(Version::new(1), Version::new(0));
assert_ne!(Version::new(1), Version::new(2));
assert_ne!(Version::new(1), Version::new(1234));
assert_ne!(Version::new(2), Version::new(0));
assert_ne!(Version::new(2), Version::new(1));
assert_ne!(Version::new(2), Version::new(1234));
assert_ne!(Version::new(1234), Version::new(0));
assert_ne!(Version::new(1234), Version::new(1));
assert_ne!(Version::new(1234), Version::new(2));
}
#[test]
fn order() {
assert!(!(Version::new(0) < Version::new(0)));
assert!(Version::new(0) <= Version::new(0));
assert!(Version::new(0) < Version::new(1));
assert!(Version::new(0) <= Version::new(1));
assert!(Version::new(0) < Version::new(2));
assert!(Version::new(0) <= Version::new(2));
assert!(!(Version::new(1) < Version::new(0)));
assert!(!(Version::new(1) <= Version::new(0)));
assert!(!(Version::new(1) < Version::new(1)));
assert!(Version::new(1) <= Version::new(1));
assert!(Version::new(1) < Version::new(2));
assert!(Version::new(1) <= Version::new(2));
}
#[test]
fn parse() {
let version: Version = "1.0".parse().unwrap();
assert_eq!(version.full, "1.0");
assert_eq!(version.minor, 0);
let version: Version = "1.1".parse().unwrap();
assert_eq!(version.full, "1.1");
assert_eq!(version.minor, 1);
let version: Version = "1.2".parse().unwrap();
assert_eq!(version.full, "1.2");
assert_eq!(version.minor, 2);
}
}

5
src/mod.rs Normal file
View file

@ -0,0 +1,5 @@
mod builders;
mod entities;
pub use builders::*;
pub use entities::*;