use std::fmt;
use crate::manifest::{MantarayNode, is_null_address};
use crate::swarm::{Error, Reference};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ResourceLocator {
Reference(Reference),
Ens(String),
}
impl ResourceLocator {
pub fn parse(input: &str) -> Result<Self, Error> {
if input.contains(".eth") {
return Ok(Self::Ens(input.to_owned()));
}
Ok(Self::Reference(Reference::from_hex(input)?))
}
pub fn from_reference(r: Reference) -> Self {
Self::Reference(r)
}
pub fn from_ens(name: impl Into<String>) -> Result<Self, Error> {
let name = name.into();
if name.is_empty() {
return Err(Error::argument("empty ENS name"));
}
Ok(Self::Ens(name))
}
}
impl fmt::Display for ResourceLocator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResourceLocator::Reference(r) => f.write_str(&r.to_hex()),
ResourceLocator::Ens(name) => f.write_str(name),
}
}
}
impl From<Reference> for ResourceLocator {
fn from(r: Reference) -> Self {
Self::Reference(r)
}
}
pub fn resolve_path(manifest: &MantarayNode, path: &str) -> Result<Reference, Error> {
let trimmed = path.trim_start_matches('/');
let node = manifest
.find(trimmed.as_bytes())
.ok_or_else(|| Error::argument(format!("no manifest entry for path {path:?}")))?;
target_reference(node)
.ok_or_else(|| Error::argument(format!("manifest entry for {path:?} has no target")))
}
pub fn target_reference(node: &MantarayNode) -> Option<Reference> {
if is_null_address(&node.target_address) {
return None;
}
Reference::new(&node.target_address).ok()
}
#[cfg(test)]
mod tests {
use super::*;
fn ref_at(byte: u8) -> Reference {
Reference::from_hex(&format!("{byte:02x}").repeat(32)).unwrap()
}
#[test]
fn resource_locator_parses_ens_names() {
let loc = ResourceLocator::parse("hello.eth").unwrap();
assert_eq!(loc.to_string(), "hello.eth");
assert!(matches!(loc, ResourceLocator::Ens(_)));
}
#[test]
fn resource_locator_parses_hex_reference() {
let hex = "ab".repeat(32);
let loc = ResourceLocator::parse(&hex).unwrap();
assert_eq!(loc.to_string(), hex);
assert!(matches!(loc, ResourceLocator::Reference(_)));
}
#[test]
fn resource_locator_rejects_invalid_hex() {
assert!(ResourceLocator::parse("not-hex").is_err());
}
#[test]
fn resource_locator_from_reference_round_trip() {
let r = Reference::from_hex(&"cd".repeat(32)).unwrap();
let loc = ResourceLocator::from_reference(r.clone());
assert_eq!(loc.to_string(), r.to_hex());
}
#[test]
fn resource_locator_from_ens_rejects_empty() {
assert!(ResourceLocator::from_ens("").is_err());
}
#[test]
fn resolve_path_finds_added_fork() {
let mut m = MantarayNode::new();
let target = ref_at(0xaa);
m.add_fork(b"index.html", Some(&target), None);
let got = resolve_path(&m, "/index.html").unwrap();
assert_eq!(got, target);
}
#[test]
fn resolve_path_handles_nested_paths() {
let mut m = MantarayNode::new();
let leaf = ref_at(0xbb);
m.add_fork(b"assets/logo.png", Some(&leaf), None);
let got = resolve_path(&m, "assets/logo.png").unwrap();
assert_eq!(got, leaf);
}
#[test]
fn resolve_path_returns_error_for_missing_path() {
let m = MantarayNode::new();
assert!(resolve_path(&m, "/nope").is_err());
}
#[test]
fn resolve_path_with_leading_slash_works() {
let mut m = MantarayNode::new();
let leaf = ref_at(0xcc);
m.add_fork(b"a", Some(&leaf), None);
let got = resolve_path(&m, "/a").unwrap();
assert_eq!(got, leaf);
}
}