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:
commit
5ef22483f0
23 changed files with 2464 additions and 0 deletions
96
src/builders/data.rs
Normal file
96
src/builders/data.rs
Normal 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
231
src/builders/document.rs
Normal 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
116
src/builders/jsonapi.rs
Normal 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
75
src/builders/link.rs
Normal 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
282
src/builders/links.rs
Normal 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())),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
94
src/builders/meta_or_attrs.rs
Normal file
94
src/builders/meta_or_attrs.rs
Normal 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
35
src/builders/mod.rs
Normal 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()
|
||||
}
|
||||
}
|
137
src/builders/relationship.rs
Normal file
137
src/builders/relationship.rs
Normal 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,
|
||||
})),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
109
src/builders/relationships.rs
Normal file
109
src/builders/relationships.rs
Normal 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
292
src/builders/resource.rs
Normal 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
58
src/entities/data.rs
Normal 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
11
src/entities/document.rs
Normal 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
9
src/entities/jsonapi.rs
Normal 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
56
src/entities/link.rs
Normal 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)
|
||||
}
|
||||
}
|
9
src/entities/link_object.rs
Normal file
9
src/entities/link_object.rs
Normal 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
101
src/entities/links.rs
Normal 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)
|
||||
}
|
||||
}
|
5
src/entities/meta_or_attrs.rs
Normal file
5
src/entities/meta_or_attrs.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use super::*;
|
||||
|
||||
impl Entity for MetaOrAttrs {}
|
||||
|
||||
pub type MetaOrAttrs = HashMap<String, Value>;
|
511
src/entities/mod.rs
Normal file
511
src/entities/mod.rs
Normal 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),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
10
src/entities/relationship.rs
Normal file
10
src/entities/relationship.rs
Normal 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>,
|
||||
}
|
5
src/entities/relationships.rs
Normal file
5
src/entities/relationships.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use super::*;
|
||||
|
||||
impl Entity for Relationships {}
|
||||
|
||||
pub type Relationships = HashMap<String, Relationship>;
|
14
src/entities/resource.rs
Normal file
14
src/entities/resource.rs
Normal 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
203
src/entities/version.rs
Normal 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
5
src/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod builders;
|
||||
mod entities;
|
||||
|
||||
pub use builders::*;
|
||||
pub use entities::*;
|
Loading…
Add table
Reference in a new issue