use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Subject {
pub subject_type: String,
pub subject_id: String,
pub relation: Option<String>,
}
impl Subject {
pub fn new(subject_type: impl Into<String>, subject_id: impl Into<String>) -> Self {
Subject {
subject_type: subject_type.into(),
subject_id: subject_id.into(),
relation: None,
}
}
pub fn with_relation(
subject_type: impl Into<String>,
subject_id: impl Into<String>,
relation: impl Into<String>,
) -> Self {
Subject {
subject_type: subject_type.into(),
subject_id: subject_id.into(),
relation: Some(relation.into()),
}
}
pub fn parse(s: &str) -> Result<Self, String> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 2 {
return Err(format!("Invalid subject format: {}", s));
}
let subject_type = parts[0].to_string();
let id_and_relation = parts[1];
if id_and_relation.contains('#') {
let rel_parts: Vec<&str> = id_and_relation.split('#').collect();
if rel_parts.len() != 2 {
return Err(format!("Invalid subject relation format: {}", s));
}
Ok(Subject {
subject_type,
subject_id: rel_parts[0].to_string(),
relation: Some(rel_parts[1].to_string()),
})
} else {
Ok(Subject {
subject_type,
subject_id: id_and_relation.to_string(),
relation: None,
})
}
}
pub fn to_string_format(&self) -> String {
if let Some(ref rel) = self.relation {
format!("{}:{}#{}", self.subject_type, self.subject_id, rel)
} else {
format!("{}:{}", self.subject_type, self.subject_id)
}
}
}
impl fmt::Display for Subject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string_format())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Relation {
pub name: String,
}
impl Relation {
pub fn new(name: impl Into<String>) -> Self {
Relation { name: name.into() }
}
pub fn parse(s: &str) -> Result<Self, String> {
if s.is_empty() {
return Err("Relation cannot be empty".to_string());
}
Ok(Relation {
name: s.to_string(),
})
}
}
impl fmt::Display for Relation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Object {
pub object_type: String,
pub object_id: String,
}
impl Object {
pub fn new(object_type: impl Into<String>, object_id: impl Into<String>) -> Self {
Object {
object_type: object_type.into(),
object_id: object_id.into(),
}
}
pub fn parse(s: &str) -> Result<Self, String> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 2 {
return Err(format!("Invalid object format: {}", s));
}
Ok(Object {
object_type: parts[0].to_string(),
object_id: parts[1].to_string(),
})
}
pub fn to_string_format(&self) -> String {
format!("{}:{}", self.object_type, self.object_id)
}
}
impl fmt::Display for Object {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string_format())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RelationTuple {
pub subject: Subject,
pub relation: Relation,
pub object: Object,
}
impl RelationTuple {
pub fn new(subject: Subject, relation: Relation, object: Object) -> Self {
RelationTuple {
subject,
relation,
object,
}
}
pub fn parse(s: &str) -> Result<Self, String> {
let parts: Vec<&str> = s.split_whitespace().collect();
if parts.len() != 3 {
return Err(format!("Invalid tuple format: {}", s));
}
Ok(RelationTuple {
subject: Subject::parse(parts[0])?,
relation: Relation::parse(parts[1])?,
object: Object::parse(parts[2])?,
})
}
pub fn to_string_format(&self) -> String {
format!(
"{} {} {}",
self.subject.to_string_format(),
self.relation.name,
self.object.to_string_format()
)
}
}
impl fmt::Display for RelationTuple {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string_format())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subject_parse() {
let subject = Subject::parse("user:alice").expect("parse should succeed");
assert_eq!(subject.subject_type, "user");
assert_eq!(subject.subject_id, "alice");
assert_eq!(subject.relation, None);
let subject_with_rel =
Subject::parse("team:engineering#member").expect("parse should succeed");
assert_eq!(subject_with_rel.subject_type, "team");
assert_eq!(subject_with_rel.subject_id, "engineering");
assert_eq!(subject_with_rel.relation, Some("member".to_string()));
}
#[test]
fn test_subject_to_string() {
let subject = Subject::new("user", "alice");
assert_eq!(subject.to_string_format(), "user:alice");
let subject_with_rel = Subject::with_relation("team", "engineering", "member");
assert_eq!(
subject_with_rel.to_string_format(),
"team:engineering#member"
);
}
#[test]
fn test_object_parse() {
let object = Object::parse("document:123").expect("parse should succeed");
assert_eq!(object.object_type, "document");
assert_eq!(object.object_id, "123");
}
#[test]
fn test_relation_tuple_parse() {
let tuple =
RelationTuple::parse("user:alice owner document:123").expect("parse should succeed");
assert_eq!(tuple.subject.subject_id, "alice");
assert_eq!(tuple.relation.name, "owner");
assert_eq!(tuple.object.object_id, "123");
}
#[test]
fn test_relation_tuple_to_string() {
let tuple = RelationTuple::new(
Subject::new("user", "alice"),
Relation::new("owner"),
Object::new("document", "123"),
);
assert_eq!(tuple.to_string_format(), "user:alice owner document:123");
}
}