use crate::{Error, Result, SelfHref, mime::APPLICATION_GEOJSON};
use mime::APPLICATION_JSON;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use stac_derive::Fields;
pub const CHILD_REL: &str = "child";
pub const ITEM_REL: &str = "item";
pub const PARENT_REL: &str = "parent";
pub const ROOT_REL: &str = "root";
pub const SELF_REL: &str = "self";
pub const COLLECTION_REL: &str = "collection";
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Fields)]
pub struct Link {
pub href: String,
pub rel: String,
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<Map<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<Map<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub merge: Option<bool>,
#[serde(flatten)]
pub additional_fields: Map<String, Value>,
}
pub trait Links: SelfHref {
fn links(&self) -> &[Link];
fn links_mut(&mut self) -> &mut Vec<Link>;
fn link(&self, rel: &str) -> Option<&Link> {
self.links().iter().find(|link| link.rel == rel)
}
fn set_link(&mut self, link: Link) {
self.links_mut().retain(|l| l.rel != link.rel);
self.links_mut().push(link)
}
fn root_link(&self) -> Option<&Link> {
self.links().iter().find(|link| link.is_root())
}
fn self_link(&self) -> Option<&Link> {
self.links().iter().find(|link| link.is_self())
}
fn parent_link(&self) -> Option<&Link> {
self.links().iter().find(|link| link.is_parent())
}
fn iter_child_links(&self) -> Box<dyn Iterator<Item = &Link> + '_> {
Box::new(self.links().iter().filter(|link| link.is_child()))
}
fn iter_item_links(&self) -> Box<dyn Iterator<Item = &Link> + '_> {
Box::new(self.links().iter().filter(|link| link.is_item()))
}
fn make_links_absolute(&mut self) -> Result<()> {
if let Some(href) = self.self_href() {
let href = href.to_string();
for link in self.links_mut() {
link.make_absolute(&href)?;
}
Ok(())
} else {
Err(Error::NoHref)
}
}
fn make_links_relative(&mut self) -> Result<()> {
if let Some(href) = self.self_href() {
let href = href.to_string();
for link in self.links_mut() {
link.make_relative(&href);
}
Ok(())
} else {
Err(Error::NoHref)
}
}
fn remove_relative_links(&mut self) {
self.links_mut().retain(|link| link.is_absolute())
}
fn remove_structural_links(&mut self) {
self.links_mut().retain(|link| !link.is_structural())
}
}
impl Link {
pub fn new(href: impl ToString, rel: impl ToString) -> Link {
Link {
href: href.to_string(),
rel: rel.to_string(),
r#type: None,
title: None,
method: None,
headers: None,
body: None,
merge: None,
additional_fields: Map::new(),
}
}
pub fn json(mut self) -> Link {
self.r#type = Some(APPLICATION_JSON.to_string());
self
}
pub fn is_json(&self) -> bool {
self.r#type
.as_ref()
.map(|t| t == APPLICATION_JSON.as_ref())
.unwrap_or(false)
}
pub fn geojson(mut self) -> Link {
self.r#type = Some(APPLICATION_GEOJSON.to_string());
self
}
pub fn is_geojson(&self) -> bool {
self.r#type
.as_ref()
.map(|t| t == APPLICATION_GEOJSON)
.unwrap_or(false)
}
pub fn r#type(mut self, r#type: impl Into<Option<String>>) -> Link {
self.r#type = r#type.into();
self
}
pub fn title(mut self, title: impl Into<Option<String>>) -> Link {
self.title = title.into();
self
}
pub fn root(href: impl ToString) -> Link {
Link::new(href, ROOT_REL).json()
}
pub fn self_(href: impl ToString) -> Link {
Link::new(href, SELF_REL).json()
}
pub fn child(href: impl ToString) -> Link {
Link::new(href, CHILD_REL).json()
}
pub fn item(href: impl ToString) -> Link {
Link::new(href, ITEM_REL).json()
}
pub fn parent(href: impl ToString) -> Link {
Link::new(href, PARENT_REL).json()
}
pub fn collection(href: impl ToString) -> Link {
Link::new(href, COLLECTION_REL).json()
}
pub fn is_item(&self) -> bool {
self.rel == ITEM_REL
}
pub fn is_child(&self) -> bool {
self.rel == CHILD_REL
}
pub fn is_parent(&self) -> bool {
self.rel == PARENT_REL
}
pub fn is_root(&self) -> bool {
self.rel == ROOT_REL
}
pub fn is_self(&self) -> bool {
self.rel == SELF_REL
}
pub fn is_collection(&self) -> bool {
self.rel == COLLECTION_REL
}
pub fn is_structural(&self) -> bool {
self.is_child()
|| self.is_item()
|| self.is_parent()
|| self.is_root()
|| self.is_self()
|| self.is_collection()
|| self.rel == "data"
|| self.rel == "conformance"
|| self.rel == "items"
|| self.rel == "search"
|| self.rel == "service-desc"
|| self.rel == "service-doc"
|| self.rel == "next"
|| self.rel == "prev"
}
pub fn is_absolute(&self) -> bool {
crate::href::is_absolute(&self.href)
}
pub fn is_relative(&self) -> bool {
!crate::href::is_absolute(&self.href)
}
pub fn method(mut self, method: impl ToString) -> Link {
self.method = Some(method.to_string());
self
}
pub fn body<T: Serialize>(mut self, body: T) -> Result<Link> {
match serde_json::to_value(body)? {
Value::Object(body) => {
self.body = Some(body);
Ok(self)
}
value => Err(Error::IncorrectType {
actual: value.to_string(),
expected: "object".to_string(),
}),
}
}
pub fn make_absolute(&mut self, base: &str) -> Result<()> {
self.href = crate::href::make_absolute(&self.href, base)?.into_owned();
Ok(())
}
pub fn make_relative(&mut self, base: &str) {
self.href = crate::href::make_relative(&self.href, base);
}
}
#[cfg(test)]
mod tests {
use super::Link;
#[test]
fn new() {
let link = Link::new("an-href", "a-rel");
assert_eq!(link.href, "an-href");
assert_eq!(link.rel, "a-rel");
assert!(link.r#type.is_none());
assert!(link.title.is_none());
}
#[test]
fn skip_serializing() {
let link = Link::new("an-href", "a-rel");
let value = serde_json::to_value(link).unwrap();
assert!(value.get("type").is_none());
assert!(value.get("title").is_none());
}
mod links {
use crate::{Catalog, Item, Link, Links};
#[test]
fn link() {
let mut item = Item::new("an-item");
assert!(item.link("root").is_none());
item.links.push(Link::new("an-href", "root"));
assert!(item.link("root").is_some());
}
#[test]
fn root() {
let mut item = Item::new("an-item");
assert!(item.root_link().is_none());
item.links.push(Link::new("an-href", "root"));
assert!(item.root_link().is_some());
}
#[test]
fn self_() {
let mut item = Item::new("an-item");
assert!(item.self_link().is_none());
item.links.push(Link::new("an-href", "self"));
assert!(item.self_link().is_some());
}
#[test]
fn remove_relative_links() {
let mut catalog = Catalog::new("an-id", "a description");
catalog.links.push(Link::new("./child.json", "child"));
catalog.links.push(Link::new("/child.json", "child"));
catalog
.links
.push(Link::new("http://rustac.test/child.json", "child"));
catalog.remove_relative_links();
assert_eq!(catalog.links.len(), 2);
}
}
}