use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode};
use crate::model::{Link, Links};
const PATH_SEGMENT: &AsciiSet = &CONTROLS
.add(b' ')
.add(b'"')
.add(b'#')
.add(b'%')
.add(b'/')
.add(b'<')
.add(b'>')
.add(b'?')
.add(b'[')
.add(b'\\')
.add(b']')
.add(b'^')
.add(b'`')
.add(b'{')
.add(b'|')
.add(b'}');
fn trim_base(base: &str) -> &str {
base.strip_suffix('/').unwrap_or(base)
}
fn seg(id: &str) -> String {
utf8_percent_encode(id, PATH_SEGMENT).to_string()
}
#[must_use]
pub fn collection_self(base: &str, type_: &str) -> String {
format!("{}/{}", trim_base(base), type_)
}
#[must_use]
pub fn resource_self(base: &str, type_: &str, id: &str) -> String {
format!("{}/{}/{}", trim_base(base), type_, seg(id))
}
#[must_use]
pub fn relationship_self(base: &str, type_: &str, id: &str, name: &str) -> String {
format!(
"{}/{}/{}/relationships/{}",
trim_base(base),
type_,
seg(id),
name
)
}
#[must_use]
pub fn relationship_related(base: &str, type_: &str, id: &str, name: &str) -> String {
format!("{}/{}/{}/{}", trim_base(base), type_, seg(id), name)
}
#[must_use]
pub fn resource_self_links(base: &str, type_: &str, id: &str) -> Links {
let mut links = Links::new();
links.insert("self", Some(Link::String(resource_self(base, type_, id))));
links
}
#[must_use]
pub fn collection_self_links(base: &str, type_: &str) -> Links {
let mut links = Links::new();
links.insert("self", Some(Link::String(collection_self(base, type_))));
links
}
#[must_use]
pub fn relationship_links(base: &str, type_: &str, id: &str, name: &str) -> Links {
let mut links = Links::new();
links.insert(
"self",
Some(Link::String(relationship_self(base, type_, id, name))),
);
links.insert(
"related",
Some(Link::String(relationship_related(base, type_, id, name))),
);
links
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resource_and_collection_self_links() {
assert_eq!(
collection_self("https://api.test", "articles"),
"https://api.test/articles"
);
assert_eq!(
resource_self("https://api.test", "articles", "1"),
"https://api.test/articles/1"
);
}
#[test]
fn trailing_slash_in_base_is_not_doubled() {
assert_eq!(
resource_self("https://api.test/", "articles", "1"),
"https://api.test/articles/1"
);
}
#[test]
fn relationship_self_and_related_links() {
assert_eq!(
relationship_self("https://api.test", "articles", "1", "author"),
"https://api.test/articles/1/relationships/author"
);
assert_eq!(
relationship_related("https://api.test", "articles", "1", "author"),
"https://api.test/articles/1/author"
);
}
#[test]
fn id_is_percent_encoded_as_a_path_segment() {
assert_eq!(
resource_self("https://api.test", "articles", "a/b c"),
"https://api.test/articles/a%2Fb%20c"
);
assert_eq!(
resource_self("https://api.test", "articles", "a-b_c.d~e"),
"https://api.test/articles/a-b_c.d~e"
);
}
#[test]
fn links_maps_carry_self_and_related() {
let links = resource_self_links("https://api.test", "articles", "1");
assert!(
matches!(links.get("self"), Some(Link::String(s)) if s == "https://api.test/articles/1")
);
let rel = relationship_links("https://api.test", "articles", "1", "author");
assert!(rel.get("self").is_some());
assert!(rel.get("related").is_some());
}
}