use core::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use crate::val::Value;
#[derive(Clone, Debug)]
pub struct ObjectKey(String);
impl Default for ObjectKey {
fn default() -> Self {
ObjectKey("/".to_owned())
}
}
impl ObjectKey {
pub fn new(path: impl Into<String>) -> Self {
let path_str = path.into();
let normalized = if path_str.starts_with('/') {
path_str
} else {
format!("/{}", path_str)
};
Self(normalized)
}
pub fn join(&self, right: &ObjectKey) -> Self {
let mut path = self.0.clone();
if path.ends_with('/') {
path.pop();
}
path.push_str(&right.0);
Self(path)
}
pub fn strip_prefix(&self, prefix: &str) -> Option<Self> {
let normalized_prefix = if prefix.starts_with('/') {
prefix.to_string()
} else {
format!("/{}", prefix)
};
let normalized_prefix = normalized_prefix.trim_end_matches('/');
if self.0.starts_with(normalized_prefix) {
let remaining = &self.0[normalized_prefix.len()..];
let result = if remaining.is_empty() || remaining.starts_with('/') {
remaining.to_string()
} else {
format!("/{}", remaining)
};
Some(Self(result))
} else {
None
}
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn check_no_traversal(&self) -> Result<(), String> {
for segment in self.0.split('/') {
if segment == ".." || segment == "." {
return Err(format!("object key '{}' contains a path-traversal segment", self.0));
}
}
Ok(())
}
}
impl From<ObjectKey> for Value {
fn from(val: ObjectKey) -> Self {
Value::from(val.0)
}
}
impl Deref for ObjectKey {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl fmt::Display for ObjectKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl PartialEq for ObjectKey {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for ObjectKey {}
impl Hash for ObjectKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl PartialOrd for ObjectKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ObjectKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}