#![warn(clippy::doc_markdown, missing_docs)]
#![deny(
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications
)]
use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum InvalidPrefixError {
ReservedPrefix,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ExpansionError {
Invalid,
MissingDefault,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PrefixMapping {
default: Option<String>,
mapping: indexmap::IndexMap<String, String>,
}
impl PrefixMapping {
pub fn set_default(&mut self, default: &str) {
self.default = Some(String::from(default));
}
pub fn add_prefix(&mut self, prefix: &str, value: &str) -> Result<(), InvalidPrefixError> {
if prefix == "_" {
Err(InvalidPrefixError::ReservedPrefix)
} else {
self.mapping
.insert(String::from(prefix), String::from(value));
Ok(())
}
}
pub fn remove_prefix(&mut self, prefix: &str) {
self.mapping.shift_remove(prefix);
}
pub fn expand_curie_string(&self, curie_str: &str) -> Result<String, ExpansionError> {
if let Some(separator_idx) = curie_str.chars().position(|c| c == ':') {
let prefix = Some(&curie_str[..separator_idx]);
let reference = &curie_str[separator_idx + 1..];
let curie = Curie::new(prefix, reference);
self.expand_curie(&curie)
} else {
let curie = Curie::new(None, curie_str);
self.expand_curie(&curie)
}
}
pub fn expand_curie(&self, curie: &Curie) -> Result<String, ExpansionError> {
self.expand_exploded_curie(curie.prefix, curie.reference)
}
fn expand_exploded_curie(
&self,
prefix: Option<&str>,
reference: &str,
) -> Result<String, ExpansionError> {
if let Some(prefix) = prefix {
if let Some(mapped_prefix) = self.mapping.get(prefix) {
Ok((*mapped_prefix).clone() + reference)
} else {
Err(ExpansionError::Invalid)
}
} else if let Some(ref default) = self.default {
Ok((default).clone() + reference)
} else {
Err(ExpansionError::MissingDefault)
}
}
pub fn shrink_iri<'a>(&'a self, iri: &'a str) -> Result<Curie<'a>, &'static str> {
if let Some(ref def) = self.default {
if iri.starts_with(def) {
return Ok(Curie::new(None, iri.trim_start_matches(def)));
}
}
for mp in &self.mapping {
if iri.starts_with(mp.1) {
return Ok(Curie::new(Some(mp.0), iri.trim_start_matches(mp.1)));
}
}
Err("Unable to shorten")
}
#[must_use]
pub fn mappings(&self) -> indexmap::map::Iter<String, String> {
self.mapping.iter()
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct Curie<'c> {
prefix: Option<&'c str>,
reference: &'c str,
}
impl<'c> Curie<'c> {
#[must_use]
pub fn new(prefix: Option<&'c str>, reference: &'c str) -> Self {
Curie { prefix, reference }
}
}
impl<'c> From<&'c Curie<'c>> for String {
fn from(c: &'c Curie<'c>) -> String {
format!("{c}")
}
}
impl<'c> From<Curie<'c>> for String {
fn from(c: Curie<'c>) -> String {
format!("{c}")
}
}
impl fmt::Display for Curie<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.prefix {
Some(prefix) => write!(f, "{}:{}", prefix, self.reference),
None => write!(f, "{}", self.reference),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const FOAF_VOCAB: &str = "http://xmlns.com/foaf/0.1/";
#[test]
fn add_remove_works() {
let mut pm = PrefixMapping::default();
assert_eq!(pm.mapping.get("foaf"), None);
assert_eq!(pm.add_prefix("foaf", FOAF_VOCAB), Ok(()));
assert_eq!(pm.mapping.get("foaf"), Some(&String::from(FOAF_VOCAB)));
assert_eq!(pm.mapping.get("rdfs"), None);
assert_eq!(
pm.add_prefix("_", ""),
Err(InvalidPrefixError::ReservedPrefix)
);
pm.remove_prefix("foaf");
assert_eq!(pm.mapping.get("foaf"), None);
}
#[test]
fn display_curie() {
let curie = Curie::new(Some("foaf"), "Agent");
assert_eq!("foaf:Agent", format!("{curie}"));
}
#[test]
fn from_string_curie() {
let curie = Curie::new(Some("foaf"), "Agent");
assert_eq!("foaf:Agent", String::from(curie));
let curie = Curie::new(None, "Agent");
assert_eq!("Agent", String::from(curie));
let curie = Curie::new(Some("foaf"), "Agent");
assert_eq!("foaf:Agent", String::from(&curie));
}
#[test]
fn expand_curie_string() {
let mut mapping = PrefixMapping::default();
let curie = "foaf:Person";
assert_eq!(
mapping.expand_curie_string(curie),
Err(ExpansionError::Invalid)
);
assert_eq!(
mapping.expand_curie_string("Person"),
Err(ExpansionError::MissingDefault)
);
mapping.set_default("http://example.com/");
assert_eq!(
mapping.expand_curie_string("Person"),
Ok(String::from("http://example.com/Person"))
);
assert_eq!(
mapping.expand_curie_string(":Person"),
Err(ExpansionError::Invalid)
);
mapping
.add_prefix("", "http://example.com/ExampleDocument#")
.unwrap();
assert_eq!(
mapping.expand_curie_string(":Person"),
Ok(String::from("http://example.com/ExampleDocument#Person"))
);
assert_eq!(
mapping.expand_curie_string(curie),
Err(ExpansionError::Invalid)
);
mapping.add_prefix("foaf", FOAF_VOCAB).unwrap();
assert_eq!(
mapping.expand_curie_string(curie),
Ok(String::from("http://xmlns.com/foaf/0.1/Person"))
);
}
#[test]
fn expand_curie() {
let mut mapping = PrefixMapping::default();
mapping.add_prefix("foaf", FOAF_VOCAB).unwrap();
let curie = Curie::new(Some("foaf"), "Agent");
assert_eq!(
mapping.expand_curie(&curie),
Ok(String::from("http://xmlns.com/foaf/0.1/Agent"))
);
}
#[test]
fn expand_curie_default() {
let mut mapping = PrefixMapping::default();
mapping.set_default(FOAF_VOCAB);
let curie = Curie::new(None, "Agent");
assert_eq!(
mapping.expand_curie(&curie),
Ok(String::from("http://xmlns.com/foaf/0.1/Agent"))
);
}
#[test]
fn shrink_iri_prefix() {
let mut mapping = PrefixMapping::default();
mapping.add_prefix("foaf", FOAF_VOCAB).unwrap();
let curie = Curie::new(Some("foaf"), "Agent");
assert_eq!(
mapping.shrink_iri("http://xmlns.com/foaf/0.1/Agent"),
Ok(curie)
);
}
#[test]
fn shrink_iri_precedence() {
let mut mapping = PrefixMapping::default();
mapping.add_prefix("a", "http://example.com/").unwrap();
mapping
.add_prefix("b", "http://example.com/other/")
.unwrap();
assert_eq!(
mapping.shrink_iri("http://example.com/thing"),
Ok(Curie::new(Some("a"), "thing")),
);
assert_eq!(
mapping.shrink_iri("http://example.com/other/thing"),
Ok(Curie::new(Some("a"), "other/thing")),
);
let mut mapping2 = PrefixMapping::default();
mapping2
.add_prefix("b", "http://example.com/other/")
.unwrap();
mapping2.add_prefix("a", "http://example.com/").unwrap();
assert_eq!(
mapping2.shrink_iri("http://example.com/thing"),
Ok(Curie::new(Some("a"), "thing")),
);
assert_eq!(
mapping2.shrink_iri("http://example.com/other/thing"),
Ok(Curie::new(Some("b"), "thing")),
);
}
#[test]
fn split_iri_default() {
let mut mapping = PrefixMapping::default();
mapping.set_default(FOAF_VOCAB);
let curie = Curie::new(None, "Agent");
assert_eq!(
mapping.shrink_iri("http://xmlns.com/foaf/0.1/Agent"),
Ok(curie)
);
}
#[test]
fn prefix_mapping_equality() {
let mut m1 = PrefixMapping::default();
let mut m2 = PrefixMapping::default();
assert_eq!(m1, m2);
m1.set_default(FOAF_VOCAB);
assert_ne!(m1, m2);
m2.set_default(FOAF_VOCAB);
assert_eq!(m1, m2);
m1.add_prefix("foaf", FOAF_VOCAB).unwrap();
assert_ne!(m1, m2);
m2.add_prefix("foaf", FOAF_VOCAB).unwrap();
assert_eq!(m1, m2);
}
}