#![crate_name = "hal"]
#![crate_type = "lib"]
#[warn(non_camel_case_types)]
extern crate rustc_serialize as serialize;
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::BTreeMap;
use serialize::json::{ToJson, Json};
use serialize::{json};
#[cfg(test)]
mod tests;
#[derive(Clone, PartialEq, Debug)]
pub enum HalState {
I64(i64),
F64(f64),
U64(u64),
HalString(String),
Boolean(bool),
Null,
HalList(List),
Object(HalObject),
}
pub type List = Vec<HalState>;
pub type HalObject = BTreeMap<String, HalState>;
pub trait ToHalState {
fn to_hal_state(&self) -> HalState;
}
impl ToHalState for isize {
fn to_hal_state(&self) -> HalState { HalState::I64(*self as i64) }
}
impl ToHalState for i64 {
fn to_hal_state(&self) -> HalState { HalState::I64(*self) }
}
impl ToHalState for u64 {
fn to_hal_state(&self) -> HalState { HalState::U64(*self) }
}
impl ToHalState for f64 {
fn to_hal_state(&self) -> HalState { HalState::F64(*self) }
}
impl ToHalState for () {
fn to_hal_state(&self) -> HalState { HalState::Null }
}
impl ToHalState for bool {
fn to_hal_state(&self) -> HalState { HalState::Boolean(*self) }
}
impl ToHalState for String {
fn to_hal_state(&self) -> HalState { HalState::HalString((*self).clone()) }
}
impl ToHalState for &'static str {
fn to_hal_state(&self) -> HalState { HalState::HalString((*self).to_string()) }
}
impl<T:ToHalState> ToHalState for Vec<T> {
fn to_hal_state(&self) -> HalState { HalState::HalList(self.iter().map(|elt| elt.to_hal_state()).collect()) }
}
impl<T:ToHalState> ToHalState for BTreeMap<String, T> {
fn to_hal_state(&self) -> HalState {
let mut t = BTreeMap::new();
for (key, value) in self.iter() {
t.insert((*key).clone(), value.to_hal_state());
}
HalState::Object(t)
}
}
impl<T:ToHalState> ToHalState for HashMap<String, T> {
fn to_hal_state(&self) -> HalState {
let mut t = BTreeMap::new();
for (key, value) in self.iter() {
t.insert((*key).clone(), value.to_hal_state());
}
HalState::Object(t)
}
}
impl<T:ToHalState> ToHalState for Option<T> {
fn to_hal_state(&self) -> HalState {
match *self {
None => HalState::Null,
Some(ref value) => value.to_hal_state()
}
}
}
impl ToHalState for Json {
fn to_hal_state(&self) -> HalState {
match *self {
Json::I64(v) => v.to_hal_state(),
Json::U64(v) => v.to_hal_state(),
Json::F64(v) => v.to_hal_state(),
Json::String(ref v) => v.to_hal_state(),
Json::Boolean(v) => v.to_hal_state(),
Json::Array(ref v) => v.to_hal_state(),
Json::Object(ref v) => v.to_hal_state(),
Json::Null => ().to_hal_state(),
}
}
}
impl ToJson for HalState {
fn to_json(&self) -> Json {
match *self {
HalState::I64(v) => v.to_json(),
HalState::F64(v) => v.to_json(),
HalState::U64(v) => v.to_json(),
HalState::HalString(ref v) => v.to_json(),
HalState::Boolean(v) => v.to_json(),
HalState::Null => ().to_json(),
HalState::HalList(ref v) => v.to_json(),
HalState::Object(ref v) => v.to_json(),
}
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Link {
href: String,
templated: Option<bool>,
media_type: Option<String>,
deprecation: Option<String>,
name: Option<String>,
profile: Option<String>,
title: Option<String>,
hreflang: Option<String>,
}
impl Link {
pub fn new(href: &str) -> Link {
Link { href: href.to_string(),
templated: None,
media_type: None,
deprecation: None,
name: None,
profile: None,
title: None,
hreflang: None
}
}
pub fn from_json(json: &Json) -> Link {
let ref url = json["href".as_ref()];
let mut link = Link::new(url.as_string().unwrap());
if json.search("templated").is_some() {
let value = json.search("templated").unwrap();
link = link.templated(value.as_boolean().unwrap());
}
if json.search("type").is_some() {
let value = json.search("type").unwrap();
link = link.media_type(value.as_string().unwrap());
}
if json.search("deprecation").is_some() {
let value = json.search("deprecation").unwrap();
link = link.deprecation(value.as_string().unwrap());
}
if json.search("name").is_some() {
let value = json.search("name").unwrap();
link = link.name(value.as_string().unwrap());
}
if json.search("title").is_some() {
let value = json.search("title").unwrap();
link = link.title(value.as_string().unwrap());
}
if json.search("profile").is_some() {
let value = json.search("profile").unwrap();
link = link.profile(value.as_string().unwrap());
}
if json.search("hreflang").is_some() {
let value = json.search("hreflang").unwrap();
link = link.hreflang(value.as_string().unwrap());
}
link
}
pub fn templated(self, is_template: bool) -> Link {
let mut link = self.clone();
link.templated = Some(is_template);
link
}
pub fn media_type(self, media_type: &str) -> Link {
let mut link = self.clone();
link.media_type = Some(media_type.to_string());
link
}
pub fn deprecation(self, deprecation: &str) -> Link {
let mut link = self.clone();
link.deprecation = Some(deprecation.to_string());
link
}
pub fn name(self, name: &str) -> Link {
let mut link = self.clone();
link.name = Some(name.to_string());
link
}
pub fn title(self, title: &str) -> Link {
let mut link = self.clone();
link.title = Some(title.to_string());
link
}
pub fn profile(self, profile: &str) -> Link {
let mut link = self.clone();
link.profile = Some(profile.to_string());
link
}
pub fn hreflang(self, hreflang: &str) -> Link {
let mut link = self.clone();
link.hreflang = Some(hreflang.to_string());
link
}
}
impl ToJson for Link {
fn to_json(&self) -> json::Json {
let mut link = BTreeMap::new();
link.insert("href".to_string(), self.href.to_json());
if self.templated.is_some() {
link.insert("templated".to_string(), self.templated.to_json());
}
if self.media_type.is_some() {
link.insert("type".to_string(), self.media_type.to_json());
}
if self.deprecation.is_some() {
link.insert("deprecation".to_string(), self.deprecation.to_json());
}
if self.name.is_some() {
link.insert("name".to_string(), self.name.to_json());
}
if self.title.is_some() {
link.insert("title".to_string(), self.title.to_json());
}
if self.profile.is_some() {
link.insert("profile".to_string(), self.profile.to_json());
}
if self.hreflang.is_some() {
link.insert("hreflang".to_string(), self.hreflang.to_json());
}
json::Json::Object(link)
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct Resource {
state: HashMap<String, HalState>,
links: HashMap<String, Vec<Link>>,
resources: HashMap<String, Vec<Resource>>
}
impl Resource {
pub fn new() -> Resource {
Resource { state: HashMap::new(), links: HashMap::new(), resources: HashMap::new() }
}
pub fn with_self(uri: &str) -> Resource {
Resource::new().add_link("self", Link::new(uri))
}
pub fn from_json(json: Json) -> Resource {
let mut resource = Resource::new();
if json.is_object() {
let json = json.as_object().unwrap();
for (key, value) in json.iter() {
if key as &str == "_links" {
let links = value.as_object().unwrap();
for (link_key, link_object) in links.iter() {
resource = resource.add_link(
link_key.as_ref(),
Link::from_json(&link_object.to_json())
);
}
} else {
resource = resource.add_state(key.as_ref(), value.clone());
}
}
}
resource
}
pub fn add_state<V>(self, key: &str, value: V) -> Resource where V: ToHalState {
let mut resource = self.clone();
resource.state.insert(key.to_string(), value.to_hal_state());
resource
}
pub fn add_link(self, rel: &str, link: Link) -> Resource {
let mut resource = self.clone();
match resource.links.entry(rel.to_string()) {
Vacant(entry) => {
let l = vec![link.clone()];
entry.insert(l);
},
Occupied(entry) => {
let links = entry.into_mut();
links.push(link.clone());
}
};
resource
}
pub fn add_curie(self, name: &str, href: &str) -> Resource {
let link = Link::new(href).templated(true).name(name);
self.add_link("curies", link)
}
pub fn add_resource(self, rel: &str, resource: Resource) -> Resource {
let mut new_r = self.clone();
match new_r.resources.entry(rel.to_string()) {
Vacant(entry) => {
let r = vec![resource.clone()];
entry.insert(r);
},
Occupied(entry) => {
let resources = entry.into_mut();
resources.push(resource.clone());
}
}
new_r
}
}
impl ToJson for Resource {
fn to_json(&self) -> json::Json {
let mut hal = BTreeMap::new();
let mut link_rels = BTreeMap::new();
let mut embeds = BTreeMap::new();
if self.links.len() > 0 {
for (rel, links) in self.links.iter() {
if links.len() > 1 || (rel as &str == "curies") {
link_rels.insert(rel.clone(), (*links).to_json());
} else {
link_rels.insert(rel.clone(), links[0].to_json());
}
}
hal.insert("_links".to_string(), link_rels.to_json());
}
for (k, v) in self.state.iter() {
hal.insert(k.clone().to_string(), v.to_json());
}
if self.resources.len() > 0 {
for (rel, resources) in self.resources.iter() {
if resources.len() > 1 {
embeds.insert(rel.clone(), resources.to_json());
} else {
embeds.insert(rel.clone(), resources[0].to_json());
}
}
hal.insert("_embedded".to_string(), embeds.to_json());
}
json::Json::Object(hal)
}
}
pub trait ToHal {
fn to_hal(self) -> Resource;
}