Merge pull request #130 from heartsucker/virtual-fs

split path into TargetPath and VirtualTargetPath
diff --git a/src/bin/tuf.rs b/src/bin/tuf.rs
index a12e569..f5a267d 100644
--- a/src/bin/tuf.rs
+++ b/src/bin/tuf.rs
@@ -1,6 +1,5 @@
 extern crate clap;
 extern crate ring;
-extern crate tuf;
 
 use clap::{App, AppSettings, SubCommand, Arg, ArgMatches};
 use ring::rand::SystemRandom;
diff --git a/src/client.rs b/src/client.rs
index d7243b3..615dc88 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -53,37 +53,84 @@
 use crypto::{self, KeyId};
 use error::Error;
 use interchange::DataInterchange;
-use metadata::{MetadataVersion, RootMetadata, Role, MetadataPath, TargetPath, TargetDescription,
-               TargetsMetadata, SnapshotMetadata};
+use metadata::{MetadataVersion, RootMetadata, Role, MetadataPath, VirtualTargetPath,
+               TargetDescription, TargetsMetadata, SnapshotMetadata, TargetPath};
 use repository::Repository;
 use tuf::Tuf;
 use util::SafeReader;
 
+
+/// Translates real paths (where a file is stored) into virtual paths (how it is addressed in TUF)
+/// and back.
+///
+/// Implementations must obey the following identities for all possible inputs.
+///
+/// ```
+/// # use tuf::client::{PathTranslator, DefaultTranslator};
+/// # use tuf::metadata::{VirtualTargetPath, TargetPath};
+/// # let path = TargetPath::new("foo".into()).unwrap();
+/// # let virt = VirtualTargetPath::new("foo".into()).unwrap();
+/// # let translator = DefaultTranslator::new();
+/// assert_eq!(path,
+///            translator.virtual_to_real(&translator.real_to_virtual(&path).unwrap()).unwrap());
+/// assert_eq!(virt,
+///            translator.real_to_virtual(&translator.virtual_to_real(&virt).unwrap()).unwrap());
+/// ```
+pub trait PathTranslator {
+    /// Convert a real path into a virtual path.
+    fn real_to_virtual(&self, path: &TargetPath) -> Result<VirtualTargetPath>;
+
+    /// Convert a virtual path into a real path.
+    fn virtual_to_real(&self, path: &VirtualTargetPath) -> Result<TargetPath>;
+}
+
+/// A `PathTranslator` that does nothing.
+pub struct DefaultTranslator {}
+
+impl DefaultTranslator {
+    /// Create a new `DefaultTranslator`.
+    pub fn new() -> Self {
+        DefaultTranslator {}
+    }
+}
+
+impl PathTranslator for DefaultTranslator {
+    fn real_to_virtual(&self, path: &TargetPath) -> Result<VirtualTargetPath> {
+        VirtualTargetPath::new(path.value().into())
+    }
+
+    fn virtual_to_real(&self, path: &VirtualTargetPath) -> Result<TargetPath> {
+        TargetPath::new(path.value().into())
+    }
+}
+
 /// A client that interacts with TUF repositories.
-pub struct Client<D, L, R>
+pub struct Client<D, L, R, T>
 where
     D: DataInterchange,
     L: Repository<D>,
     R: Repository<D>,
+    T: PathTranslator,
 {
     tuf: Tuf<D>,
-    config: Config,
+    config: Config<T>,
     local: L,
     remote: R,
 }
 
-impl<D, L, R> Client<D, L, R>
+impl<D, L, R, T> Client<D, L, R, T>
 where
     D: DataInterchange,
     L: Repository<D>,
     R: Repository<D>,
+    T: PathTranslator,
 {
     /// Create a new TUF client. It will attempt to load initial root metadata from the local repo
     /// and return an error if it cannot do so.
     ///
     /// **WARNING**: This method offers weaker security guarantees than the related method
     /// `with_root_pinned`.
-    pub fn new(config: Config, mut local: L, mut remote: R) -> Result<Self> {
+    pub fn new(config: Config<T>, mut local: L, mut remote: R) -> Result<Self> {
         local.initialize()?;
         remote.initialize()?;
 
@@ -123,12 +170,13 @@
     /// This is the preferred method of creating a client.
     pub fn with_root_pinned<'a, I>(
         trusted_root_keys: I,
-        config: Config,
+        config: Config<T>,
         mut local: L,
         mut remote: R,
     ) -> Result<Self>
     where
         I: IntoIterator<Item = &'a KeyId>,
+        T: PathTranslator,
     {
         local.initialize()?;
         remote.initialize()?;
@@ -215,9 +263,10 @@
     }
 
     /// Returns `true` if an update occurred and `false` otherwise.
-    fn update_root<T>(tuf: &mut Tuf<D>, repo: &mut T, config: &Config) -> Result<bool>
+    fn update_root<V, U>(tuf: &mut Tuf<D>, repo: &mut V, config: &Config<U>) -> Result<bool>
     where
-        T: Repository<D>,
+        V: Repository<D>,
+        U: PathTranslator,
     {
         let latest_root = repo.fetch_metadata(
             &Role::Root,
@@ -266,9 +315,10 @@
     }
 
     /// Returns `true` if an update occurred and `false` otherwise.
-    fn update_timestamp<T>(tuf: &mut Tuf<D>, repo: &mut T, config: &Config) -> Result<bool>
+    fn update_timestamp<V, U>(tuf: &mut Tuf<D>, repo: &mut V, config: &Config<U>) -> Result<bool>
     where
-        T: Repository<D>,
+        V: Repository<D>,
+        U: PathTranslator,
     {
         let ts = repo.fetch_metadata(
             &Role::Timestamp,
@@ -282,9 +332,10 @@
     }
 
     /// Returns `true` if an update occurred and `false` otherwise.
-    fn update_snapshot<T>(tuf: &mut Tuf<D>, repo: &mut T, config: &Config) -> Result<bool>
+    fn update_snapshot<V, U>(tuf: &mut Tuf<D>, repo: &mut V, config: &Config<U>) -> Result<bool>
     where
-        T: Repository<D>,
+        V: Repository<D>,
+        U: PathTranslator,
     {
         let snapshot_description = match tuf.timestamp() {
             Some(ts) => Ok(ts.snapshot()),
@@ -316,9 +367,10 @@
     }
 
     /// Returns `true` if an update occurred and `false` otherwise.
-    fn update_targets<T>(tuf: &mut Tuf<D>, repo: &mut T, config: &Config) -> Result<bool>
+    fn update_targets<V, U>(tuf: &mut Tuf<D>, repo: &mut V, config: &Config<U>) -> Result<bool>
     where
-        T: Repository<D>,
+        V: Repository<D>,
+        U: PathTranslator,
     {
         let targets_description = match tuf.snapshot() {
             Some(sn) => {
@@ -360,7 +412,8 @@
 
     /// Fetch a target from the remote repo and write it to the local repo.
     pub fn fetch_target(&mut self, target: &TargetPath) -> Result<()> {
-        let read = self._fetch_target(target)?;
+        let virt = self.config.path_translator.real_to_virtual(target)?;
+        let read = self._fetch_target(&virt)?;
         self.local.store_target(read, target)
     }
 
@@ -370,7 +423,8 @@
         target: &TargetPath,
         mut write: W,
     ) -> Result<()> {
-        let mut read = self._fetch_target(target)?;
+        let virt = self.config.path_translator.real_to_virtual(target)?;
+        let mut read = self._fetch_target(&virt)?;
         let mut buf = [0; 1024];
         loop {
             let bytes_read = read.read(&mut buf)?;
@@ -383,22 +437,23 @@
     }
 
     // TODO this should check the local repo first
-    fn _fetch_target(&mut self, target: &TargetPath) -> Result<SafeReader<R::TargetRead>> {
-        fn lookup<_D, _L, _R>(
-            tuf: &mut Tuf<_D>,
-            config: &Config,
+    fn _fetch_target(&mut self, target: &VirtualTargetPath) -> Result<SafeReader<R::TargetRead>> {
+        fn lookup<D_, L_, R_, T_>(
+            tuf: &mut Tuf<D_>,
+            config: &Config<T_>,
             default_terminate: bool,
             current_depth: u32,
-            target: &TargetPath,
+            target: &VirtualTargetPath,
             snapshot: &SnapshotMetadata,
             targets: Option<&TargetsMetadata>,
-            local: &mut _L,
-            remote: &mut _R,
+            local: &mut L_,
+            remote: &mut R_,
         ) -> (bool, Result<TargetDescription>)
         where
-            _D: DataInterchange,
-            _L: Repository<_D>,
-            _R: Repository<_D>,
+            D_: DataInterchange,
+            L_: Repository<D_>,
+            R_: Repository<D_>,
+            T_: PathTranslator,
         {
             if current_depth > config.max_delegation_depth {
                 warn!(
@@ -555,7 +610,7 @@
         let target_description = target_description?;
 
         self.remote.fetch_target(
-            target,
+            &self.config.path_translator.virtual_to_real(&target)?,
             &target_description,
             self.config.min_bytes_per_second,
         )
@@ -571,27 +626,37 @@
 /// `ConfigBuilder` and set your own values.
 ///
 /// ```
-/// # use tuf::client::Config;
+/// # use tuf::client::{Config, DefaultTranslator};
 /// let config = Config::default();
 /// assert_eq!(config.max_root_size(), &Some(1024 * 1024));
 /// assert_eq!(config.max_timestamp_size(), &Some(32 * 1024));
 /// assert_eq!(config.min_bytes_per_second(), 4096);
 /// assert_eq!(config.max_delegation_depth(), 8);
+/// let _: &DefaultTranslator = config.path_translator();
 /// ```
 #[derive(Debug)]
-pub struct Config {
+pub struct Config<T>
+where
+    T: PathTranslator,
+{
     max_root_size: Option<usize>,
     max_timestamp_size: Option<usize>,
     min_bytes_per_second: u32,
     max_delegation_depth: u32,
+    path_translator: T,
 }
 
-impl Config {
+impl Config<DefaultTranslator> {
     /// Initialize a `ConfigBuilder` with the default values.
-    pub fn build() -> ConfigBuilder {
+    pub fn build() -> ConfigBuilder<DefaultTranslator> {
         ConfigBuilder::default()
     }
+}
 
+impl<T> Config<T>
+where
+    T: PathTranslator,
+{
     /// Return the optional maximum root metadata size.
     pub fn max_root_size(&self) -> &Option<usize> {
         &self.max_root_size
@@ -611,36 +676,50 @@
     pub fn max_delegation_depth(&self) -> u32 {
         self.max_delegation_depth
     }
+
+    /// The `PathTranslator`.
+    pub fn path_translator(&self) -> &T {
+        &self.path_translator
+    }
 }
 
-impl Default for Config {
+impl Default for Config<DefaultTranslator> {
     fn default() -> Self {
         Config {
             max_root_size: Some(1024 * 1024),
             max_timestamp_size: Some(32 * 1024),
             min_bytes_per_second: 4096,
             max_delegation_depth: 8,
+            path_translator: DefaultTranslator::new(),
         }
     }
 }
 
 /// Helper for building and validating a TUF client `Config`.
 #[derive(Debug, PartialEq)]
-pub struct ConfigBuilder {
+pub struct ConfigBuilder<T>
+where
+    T: PathTranslator,
+{
     max_root_size: Option<usize>,
     max_timestamp_size: Option<usize>,
     min_bytes_per_second: u32,
     max_delegation_depth: u32,
+    path_translator: T,
 }
 
-impl ConfigBuilder {
+impl<T> ConfigBuilder<T>
+where
+    T: PathTranslator,
+{
     /// Validate this builder return a `Config` if validation succeeds.
-    pub fn finish(self) -> Result<Config> {
+    pub fn finish(self) -> Result<Config<T>> {
         Ok(Config {
             max_root_size: self.max_root_size,
             max_timestamp_size: self.max_timestamp_size,
             min_bytes_per_second: self.min_bytes_per_second,
             max_delegation_depth: self.max_delegation_depth,
+            path_translator: self.path_translator,
         })
     }
 
@@ -667,16 +746,31 @@
         self.max_delegation_depth = max;
         self
     }
+
+    /// Set the `PathTranslator`.
+    pub fn path_translator<TT>(self, path_translator: TT) -> ConfigBuilder<TT>
+    where
+        TT: PathTranslator,
+    {
+        ConfigBuilder {
+            max_root_size: self.max_root_size,
+            max_timestamp_size: self.max_timestamp_size,
+            min_bytes_per_second: self.min_bytes_per_second,
+            max_delegation_depth: self.max_delegation_depth,
+            path_translator: path_translator,
+        }
+    }
 }
 
-impl Default for ConfigBuilder {
-    fn default() -> Self {
+impl Default for ConfigBuilder<DefaultTranslator> {
+    fn default() -> ConfigBuilder<DefaultTranslator> {
         let cfg = Config::default();
         ConfigBuilder {
             max_root_size: cfg.max_root_size,
             max_timestamp_size: cfg.max_timestamp_size,
             min_bytes_per_second: cfg.min_bytes_per_second,
             max_delegation_depth: cfg.max_delegation_depth,
+            path_translator: cfg.path_translator,
         }
     }
 }
diff --git a/src/metadata.rs b/src/metadata.rs
index 22eae38..7c349d0 100644
--- a/src/metadata.rs
+++ b/src/metadata.rs
@@ -230,7 +230,7 @@
     #[serde(skip_serializing, skip_deserializing)]
     _interchage: PhantomData<D>,
     #[serde(skip_serializing, skip_deserializing)]
-    _metadata: PhantomData<M>,
+    metadata: PhantomData<M>,
 }
 
 impl<D, M> SignedMetadata<D, M>
@@ -272,7 +272,7 @@
             signatures: vec![sig],
             signed: raw,
             _interchage: PhantomData,
-            _metadata: PhantomData,
+            metadata: PhantomData,
         })
     }
 
@@ -993,35 +993,35 @@
 }
 
 
-/// Wrapper for a path to a target.
+/// Wrapper for the virtual path to a target.
 #[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Serialize)]
-pub struct TargetPath(String);
+pub struct VirtualTargetPath(String);
 
-impl TargetPath {
-    /// Create a new `TargetPath` from a `String`.
+impl VirtualTargetPath {
+    /// Create a new `VirtualTargetPath` from a `String`.
     ///
     /// ```
-    /// # use tuf::metadata::TargetPath;
-    /// assert!(TargetPath::new("foo".into()).is_ok());
-    /// assert!(TargetPath::new("/foo".into()).is_err());
-    /// assert!(TargetPath::new("../foo".into()).is_err());
-    /// assert!(TargetPath::new("foo/..".into()).is_err());
-    /// assert!(TargetPath::new("foo/../bar".into()).is_err());
-    /// assert!(TargetPath::new("..foo".into()).is_ok());
-    /// assert!(TargetPath::new("foo/..bar".into()).is_ok());
-    /// assert!(TargetPath::new("foo/bar..".into()).is_ok());
+    /// # use tuf::metadata::VirtualTargetPath;
+    /// assert!(VirtualTargetPath::new("foo".into()).is_ok());
+    /// assert!(VirtualTargetPath::new("/foo".into()).is_err());
+    /// assert!(VirtualTargetPath::new("../foo".into()).is_err());
+    /// assert!(VirtualTargetPath::new("foo/..".into()).is_err());
+    /// assert!(VirtualTargetPath::new("foo/../bar".into()).is_err());
+    /// assert!(VirtualTargetPath::new("..foo".into()).is_ok());
+    /// assert!(VirtualTargetPath::new("foo/..bar".into()).is_ok());
+    /// assert!(VirtualTargetPath::new("foo/bar..".into()).is_ok());
     /// ```
     pub fn new(path: String) -> Result<Self> {
         safe_path(&path)?;
-        Ok(TargetPath(path))
+        Ok(VirtualTargetPath(path))
     }
 
-    /// Split `TargetPath` into components that can be joined to create URL paths, Unix paths, or
-    /// Windows paths.
+    /// Split `VirtualTargetPath` into components that can be joined to create URL paths, Unix
+    /// paths, or Windows paths.
     ///
     /// ```
-    /// # use tuf::metadata::TargetPath;
-    /// let path = TargetPath::new("foo/bar".into()).unwrap();
+    /// # use tuf::metadata::VirtualTargetPath;
+    /// let path = VirtualTargetPath::new("foo/bar".into()).unwrap();
     /// assert_eq!(path.components(), ["foo".to_string(), "bar".to_string()]);
     /// ```
     pub fn components(&self) -> Vec<String> {
@@ -1031,19 +1031,19 @@
     /// Return whether this path is the child of another path.
     ///
     /// ```
-    /// # use tuf::metadata::TargetPath;
-    /// let path1 = TargetPath::new("foo".into()).unwrap();
-    /// let path2 = TargetPath::new("foo/bar".into()).unwrap();
+    /// # use tuf::metadata::VirtualTargetPath;
+    /// let path1 = VirtualTargetPath::new("foo".into()).unwrap();
+    /// let path2 = VirtualTargetPath::new("foo/bar".into()).unwrap();
     /// assert!(!path2.is_child(&path1));
     ///
-    /// let path1 = TargetPath::new("foo/".into()).unwrap();
-    /// let path2 = TargetPath::new("foo/bar".into()).unwrap();
+    /// let path1 = VirtualTargetPath::new("foo/".into()).unwrap();
+    /// let path2 = VirtualTargetPath::new("foo/bar".into()).unwrap();
     /// assert!(path2.is_child(&path1));
     ///
-    /// let path2 = TargetPath::new("foo/bar/baz".into()).unwrap();
+    /// let path2 = VirtualTargetPath::new("foo/bar/baz".into()).unwrap();
     /// assert!(path2.is_child(&path1));
     ///
-    /// let path2 = TargetPath::new("wat".into()).unwrap();
+    /// let path2 = VirtualTargetPath::new("wat".into()).unwrap();
     /// assert!(!path2.is_child(&path1))
     /// ```
     pub fn is_child(&self, parent: &Self) -> bool {
@@ -1059,7 +1059,7 @@
     /// previous groups.
     // TODO this is hideous and uses way too much clone/heap but I think recursively,
     // so here we are
-    pub fn matches_chain(&self, parents: &[HashSet<TargetPath>]) -> bool {
+    pub fn matches_chain(&self, parents: &[HashSet<VirtualTargetPath>]) -> bool {
         if parents.is_empty() {
             return false;
         }
@@ -1083,18 +1083,52 @@
             .collect::<Vec<_>>();
         self.matches_chain(&*new)
     }
+
+    /// The string value of the path.
+    pub fn value(&self) -> &str {
+        &self.0
+    }
 }
 
-impl ToString for TargetPath {
+impl ToString for VirtualTargetPath {
     fn to_string(&self) -> String {
         self.0.clone()
     }
 }
 
-impl<'de> Deserialize<'de> for TargetPath {
+impl<'de> Deserialize<'de> for VirtualTargetPath {
     fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
         let s: String = Deserialize::deserialize(de)?;
-        TargetPath::new(s).map_err(|e| DeserializeError::custom(format!("{:?}", e)))
+        VirtualTargetPath::new(s).map_err(|e| DeserializeError::custom(format!("{:?}", e)))
+    }
+}
+
+/// Wrapper for the real path to a target.
+#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Serialize)]
+pub struct TargetPath(String);
+
+impl TargetPath {
+    /// Create a new `TargetPath`.
+    pub fn new(path: String) -> Result<Self> {
+        safe_path(&path)?;
+        Ok(TargetPath(path))
+    }
+
+    /// Split `TargetPath` into components that can be joined to create URL paths, Unix paths, or
+    /// Windows paths.
+    ///
+    /// ```
+    /// # use tuf::metadata::TargetPath;
+    /// let path = TargetPath::new("foo/bar".into()).unwrap();
+    /// assert_eq!(path.components(), ["foo".to_string(), "bar".to_string()]);
+    /// ```
+    pub fn components(&self) -> Vec<String> {
+        self.0.split('/').map(|s| s.to_string()).collect()
+    }
+
+    /// The string value of the path.
+    pub fn value(&self) -> &str {
+        &self.0
     }
 }
 
@@ -1189,7 +1223,7 @@
 pub struct TargetsMetadata {
     version: u32,
     expires: DateTime<Utc>,
-    targets: HashMap<TargetPath, TargetDescription>,
+    targets: HashMap<VirtualTargetPath, TargetDescription>,
     delegations: Option<Delegations>,
 }
 
@@ -1198,7 +1232,7 @@
     pub fn new(
         version: u32,
         expires: DateTime<Utc>,
-        targets: HashMap<TargetPath, TargetDescription>,
+        targets: HashMap<VirtualTargetPath, TargetDescription>,
         delegations: Option<Delegations>,
     ) -> Result<Self> {
         if version < 1 {
@@ -1227,7 +1261,7 @@
     }
 
     /// An immutable reference to the descriptions of targets.
-    pub fn targets(&self) -> &HashMap<TargetPath, TargetDescription> {
+    pub fn targets(&self) -> &HashMap<VirtualTargetPath, TargetDescription> {
         &self.targets
     }
 
@@ -1341,7 +1375,7 @@
     terminating: bool,
     threshold: u32,
     key_ids: HashSet<KeyId>,
-    paths: HashSet<TargetPath>,
+    paths: HashSet<VirtualTargetPath>,
 }
 
 impl Delegation {
@@ -1351,7 +1385,7 @@
         terminating: bool,
         threshold: u32,
         key_ids: HashSet<KeyId>,
-        paths: HashSet<TargetPath>,
+        paths: HashSet<VirtualTargetPath>,
     ) -> Result<Self> {
         if key_ids.is_empty() {
             return Err(Error::IllegalArgument("Cannot have empty key IDs".into()));
@@ -1401,7 +1435,7 @@
     }
 
     /// An immutable reference to the delegation's authorized paths.
-    pub fn paths(&self) -> &HashSet<TargetPath> {
+    pub fn paths(&self) -> &HashSet<VirtualTargetPath> {
         &self.paths
     }
 }
@@ -1469,13 +1503,13 @@
 
         for case in test_cases {
             let expected = case.0;
-            let target = TargetPath::new(case.1.into()).unwrap();
+            let target = VirtualTargetPath::new(case.1.into()).unwrap();
             let parents = case.2
                 .iter()
                 .map(|group| {
                     group
                         .iter()
-                        .map(|p| TargetPath::new(p.to_string()).unwrap())
+                        .map(|p| VirtualTargetPath::new(p.to_string()).unwrap())
                         .collect::<HashSet<_>>()
                 })
                 .collect::<Vec<_>>();
@@ -1492,7 +1526,7 @@
     #[test]
     fn serde_target_path() {
         let s = "foo/bar";
-        let t = json::from_str::<TargetPath>(&format!("\"{}\"", s)).unwrap();
+        let t = json::from_str::<VirtualTargetPath>(&format!("\"{}\"", s)).unwrap();
         assert_eq!(t.to_string().as_str(), s);
         assert_eq!(json::to_value(t).unwrap(), json!("foo/bar"));
     }
@@ -1720,7 +1754,7 @@
             1,
             Utc.ymd(2017, 1, 1).and_hms(0, 0, 0),
             hashmap! {
-                TargetPath::new("foo".into()).unwrap() =>
+                VirtualTargetPath::new("foo".into()).unwrap() =>
                     TargetDescription::from_reader(
                         b"foo" as &[u8],
                         &[HashAlgorithm::Sha256],
@@ -1759,7 +1793,7 @@
                 false,
                 1,
                 hashset!(key.key_id().clone()),
-                hashset!(TargetPath::new("baz/quux".into()).unwrap()),
+                hashset!(VirtualTargetPath::new("baz/quux".into()).unwrap()),
             ).unwrap()],
         ).unwrap();
 
@@ -1960,7 +1994,7 @@
                 false,
                 1,
                 hashset!(key.key_id().clone()),
-                hashset!(TargetPath::new("bar".into()).unwrap()),
+                hashset!(VirtualTargetPath::new("bar".into()).unwrap()),
             ).unwrap()],
         ).unwrap();
 
@@ -1977,7 +2011,7 @@
             false,
             1,
             hashset!(key.key_id().clone()),
-            hashset!(TargetPath::new("bar".into()).unwrap()),
+            hashset!(VirtualTargetPath::new("bar".into()).unwrap()),
         ).unwrap();
 
         json::to_value(&delegation).unwrap()
@@ -2300,7 +2334,7 @@
                 false,
                 1,
                 hashset!(key.key_id().clone()),
-                hashset!(TargetPath::new("bar".into()).unwrap()),
+                hashset!(VirtualTargetPath::new("bar".into()).unwrap()),
             ).unwrap()],
         ).unwrap();
         let mut delegations = json::to_value(delegations).unwrap();
diff --git a/src/repository.rs b/src/repository.rs
index 607d66d..1615bb2 100644
--- a/src/repository.rs
+++ b/src/repository.rs
@@ -100,7 +100,7 @@
     D: DataInterchange,
 {
     local_path: PathBuf,
-    _interchange: PhantomData<D>,
+    interchange: PhantomData<D>,
 }
 
 impl<D> FileSystemRepository<D>
@@ -111,7 +111,7 @@
     pub fn new(local_path: PathBuf) -> Self {
         FileSystemRepository {
             local_path: local_path,
-            _interchange: PhantomData,
+            interchange: PhantomData,
         }
     }
 }
@@ -241,7 +241,7 @@
     client: Client,
     user_agent: String,
     metadata_prefix: Option<Vec<String>>,
-    _interchange: PhantomData<D>,
+    interchange: PhantomData<D>,
 }
 
 impl<D> HttpRepository<D>
@@ -274,7 +274,7 @@
             client: client,
             user_agent: user_agent,
             metadata_prefix: metadata_prefix,
-            _interchange: PhantomData,
+            interchange: PhantomData,
         }
     }
 
@@ -399,7 +399,7 @@
 {
     metadata: HashMap<(MetadataPath, MetadataVersion), Vec<u8>>,
     targets: HashMap<TargetPath, Vec<u8>>,
-    _interchange: PhantomData<D>,
+    interchange: PhantomData<D>,
 }
 
 impl<D> EphemeralRepository<D>
@@ -411,7 +411,7 @@
         EphemeralRepository {
             metadata: HashMap::new(),
             targets: HashMap::new(),
-            _interchange: PhantomData,
+            interchange: PhantomData,
         }
     }
 }
diff --git a/src/shims.rs b/src/shims.rs
index 286af1d..e252aec 100644
--- a/src/shims.rs
+++ b/src/shims.rs
@@ -196,7 +196,7 @@
     typ: metadata::Role,
     version: u32,
     expires: String,
-    targets: HashMap<metadata::TargetPath, metadata::TargetDescription>,
+    targets: HashMap<metadata::VirtualTargetPath, metadata::TargetDescription>,
     #[serde(skip_serializing_if = "Option::is_none")]
     delegations: Option<metadata::Delegations>,
 }
@@ -269,7 +269,7 @@
     terminating: bool,
     threshold: u32,
     key_ids: Vec<crypto::KeyId>,
-    paths: Vec<metadata::TargetPath>,
+    paths: Vec<metadata::VirtualTargetPath>,
 }
 
 impl Delegation {
@@ -277,7 +277,7 @@
         let mut paths = meta.paths()
             .iter()
             .cloned()
-            .collect::<Vec<metadata::TargetPath>>();
+            .collect::<Vec<metadata::VirtualTargetPath>>();
         paths.sort();
         let mut key_ids = meta.key_ids()
             .iter()
@@ -298,7 +298,7 @@
         let paths = self.paths
             .iter()
             .cloned()
-            .collect::<HashSet<metadata::TargetPath>>();
+            .collect::<HashSet<metadata::VirtualTargetPath>>();
         if paths.len() != self.paths.len() {
             return Err(Error::Encoding("Non-unique delegation paths.".into()));
         }
diff --git a/src/tuf.rs b/src/tuf.rs
index 9b99384..eea3eec 100644
--- a/src/tuf.rs
+++ b/src/tuf.rs
@@ -9,7 +9,7 @@
 use error::Error;
 use interchange::DataInterchange;
 use metadata::{SignedMetadata, RootMetadata, TimestampMetadata, Role, SnapshotMetadata,
-               MetadataPath, TargetsMetadata, TargetPath, TargetDescription, Delegations};
+               MetadataPath, TargetsMetadata, VirtualTargetPath, TargetDescription, Delegations};
 
 /// Contains trusted TUF metadata and can be used to verify other metadata and targets.
 #[derive(Debug)]
@@ -19,7 +19,7 @@
     targets: Option<TargetsMetadata>,
     timestamp: Option<TimestampMetadata>,
     delegations: HashMap<MetadataPath, TargetsMetadata>,
-    _interchange: PhantomData<D>,
+    interchange: PhantomData<D>,
 }
 
 impl<D: DataInterchange> Tuf<D> {
@@ -65,7 +65,7 @@
             targets: None,
             timestamp: None,
             delegations: HashMap::new(),
-            _interchange: PhantomData,
+            interchange: PhantomData,
         })
     }
 
@@ -439,10 +439,10 @@
     }
 
     /// Get a reference to the description needed to verify the target defined by the given
-    /// `TargetPath`. Returns an `Error` if the target is not defined in the trusted metadata. This
-    /// may mean the target exists somewhere in the metadata, but the chain of trust to that target
-    /// may be invalid or incomplete.
-    pub fn target_description(&self, target_path: &TargetPath) -> Result<TargetDescription> {
+    /// `VirtualTargetPath`. Returns an `Error` if the target is not defined in the trusted
+    /// metadata. This may mean the target exists somewhere in the metadata, but the chain of trust
+    /// to that target may be invalid or incomplete.
+    pub fn target_description(&self, target_path: &VirtualTargetPath) -> Result<TargetDescription> {
         let _ = self.safe_root_ref()?;
         let _ = self.safe_snapshot_ref()?;
         let targets = self.safe_targets_ref()?;
@@ -456,9 +456,9 @@
             tuf: &Tuf<D>,
             default_terminate: bool,
             current_depth: u32,
-            target_path: &TargetPath,
+            target_path: &VirtualTargetPath,
             delegations: &Delegations,
-            parents: Vec<HashSet<TargetPath>>,
+            parents: Vec<HashSet<VirtualTargetPath>>,
             visited: &mut HashSet<MetadataPath>,
         ) -> (bool, Option<TargetDescription>) {
             for delegation in delegations.roles() {
diff --git a/tests/integration.rs b/tests/integration.rs
index ba8095c..4f46a83 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -10,7 +10,7 @@
 use tuf::crypto::{PrivateKey, SignatureScheme, HashAlgorithm};
 use tuf::interchange::Json;
 use tuf::metadata::{RoleDefinition, RootMetadata, MetadataPath, SignedMetadata, TargetDescription,
-                    TargetPath, TargetsMetadata, MetadataDescription, SnapshotMetadata,
+                    VirtualTargetPath, TargetsMetadata, MetadataDescription, SnapshotMetadata,
                     TimestampMetadata, Delegation, Delegations};
 
 const ED25519_1_PK8: &'static [u8] = include_bytes!("./ed25519/ed25519-1.pk8.der");
@@ -92,7 +92,7 @@
                     .iter()
                     .cloned()
                     .collect(),
-                vec![TargetPath::new("foo".into()).unwrap()]
+                vec![VirtualTargetPath::new("foo".into()).unwrap()]
                     .iter()
                     .cloned()
                     .collect()
@@ -114,7 +114,7 @@
     let target_file: &[u8] = b"bar";
     let target_map =
         hashmap! {
-        TargetPath::new("foo".into()).unwrap() =>
+        VirtualTargetPath::new("foo".into()).unwrap() =>
             TargetDescription::from_reader(target_file, &[HashAlgorithm::Sha256]).unwrap(),
     };
     let delegation =
@@ -127,7 +127,7 @@
         .unwrap();
 
     assert!(
-        tuf.target_description(&TargetPath::new("foo".into()).unwrap())
+        tuf.target_description(&VirtualTargetPath::new("foo".into()).unwrap())
             .is_ok()
     );
 }
@@ -207,7 +207,7 @@
                     .iter()
                     .cloned()
                     .collect(),
-                vec![TargetPath::new("foo".into()).unwrap()]
+                vec![VirtualTargetPath::new("foo".into()).unwrap()]
                     .iter()
                     .cloned()
                     .collect()
@@ -237,7 +237,7 @@
                     .iter()
                     .cloned()
                     .collect(),
-                vec![TargetPath::new("foo".into()).unwrap()]
+                vec![VirtualTargetPath::new("foo".into()).unwrap()]
                     .iter()
                     .cloned()
                     .collect()
@@ -261,7 +261,7 @@
     let target_file: &[u8] = b"bar";
     let target_map =
         hashmap! {
-        TargetPath::new("foo".into()).unwrap() =>
+        VirtualTargetPath::new("foo".into()).unwrap() =>
             TargetDescription::from_reader(target_file, &[HashAlgorithm::Sha256]).unwrap(),
     };
 
@@ -275,7 +275,7 @@
         .unwrap();
 
     assert!(
-        tuf.target_description(&TargetPath::new("foo".into()).unwrap())
+        tuf.target_description(&VirtualTargetPath::new("foo".into()).unwrap())
             .is_ok()
     );
 }
diff --git a/tests/simple_example.rs b/tests/simple_example.rs
index a4c451a..fe7a89c 100644
--- a/tests/simple_example.rs
+++ b/tests/simple_example.rs
@@ -5,13 +5,13 @@
 
 use chrono::prelude::*;
 use chrono::offset::Utc;
-use tuf::Error;
-use tuf::client::{Client, Config};
+use tuf::Result;
+use tuf::client::{Client, Config, PathTranslator};
 use tuf::crypto::{PrivateKey, SignatureScheme, KeyId, HashAlgorithm};
 use tuf::interchange::{DataInterchange, Json};
 use tuf::metadata::{RoleDefinition, RootMetadata, Role, MetadataVersion, MetadataPath,
-                    SignedMetadata, TargetDescription, TargetPath, TargetsMetadata,
-                    MetadataDescription, SnapshotMetadata, TimestampMetadata};
+                    SignedMetadata, TargetDescription, VirtualTargetPath, TargetsMetadata,
+                    MetadataDescription, SnapshotMetadata, TimestampMetadata, TargetPath};
 use tuf::repository::{EphemeralRepository, Repository};
 
 // Ironically, this is far from simple, but it's as simple as it can be made.
@@ -21,26 +21,59 @@
 const ED25519_3_PK8: &'static [u8] = include_bytes!("./ed25519/ed25519-3.pk8.der");
 const ED25519_4_PK8: &'static [u8] = include_bytes!("./ed25519/ed25519-4.pk8.der");
 
-#[test]
-fn main() {
-    let mut remote = EphemeralRepository::<Json>::new();
-    let root_key_ids = init_server(&mut remote).unwrap();
-    init_client(&root_key_ids, remote).unwrap();
+struct MyPathTranslator {}
+
+impl PathTranslator for MyPathTranslator {
+    fn real_to_virtual(&self, path: &TargetPath) -> Result<VirtualTargetPath> {
+        VirtualTargetPath::new(path.value().to_owned().replace("-", "/"))
+    }
+
+    fn virtual_to_real(&self, path: &VirtualTargetPath) -> Result<TargetPath> {
+        TargetPath::new(path.value().to_owned().replace("/", "-"))
+    }
 }
 
-fn init_client(root_key_ids: &[KeyId], remote: EphemeralRepository<Json>) -> Result<(), Error> {
-    let local = EphemeralRepository::<Json>::new();
+#[test]
+fn with_translator() {
+    let mut remote = EphemeralRepository::<Json>::new();
     let config = Config::default();
+    let root_key_ids = init_server(&mut remote, &config).unwrap();
+    init_client(&root_key_ids, remote, config).unwrap();
+}
+
+#[test]
+fn without_translator() {
+    let mut remote = EphemeralRepository::<Json>::new();
+    let config = Config::build()
+        .path_translator(MyPathTranslator {})
+        .finish()
+        .unwrap();
+    let root_key_ids = init_server(&mut remote, &config).unwrap();
+    init_client(&root_key_ids, remote, config).unwrap();
+}
+
+fn init_client<T>(
+    root_key_ids: &[KeyId],
+    remote: EphemeralRepository<Json>,
+    config: Config<T>,
+) -> Result<()>
+where
+    T: PathTranslator,
+{
+    let local = EphemeralRepository::<Json>::new();
     let mut client = Client::with_root_pinned(root_key_ids, config, local, remote)?;
     match client.update_local() {
         Ok(_) => (),
         Err(e) => println!("{:?}", e),
     }
     let _ = client.update_remote()?;
-    client.fetch_target(&TargetPath::new("grendel".into())?)
+    client.fetch_target(&TargetPath::new("foo-bar".into())?)
 }
 
-fn init_server(remote: &mut EphemeralRepository<Json>) -> Result<Vec<KeyId>, Error> {
+fn init_server<T>(remote: &mut EphemeralRepository<Json>, config: &Config<T>) -> Result<Vec<KeyId>>
+where
+    T: PathTranslator,
+{
     // in real life, you wouldn't want these keys on the same machine ever
     let root_key = PrivateKey::from_pkcs8(ED25519_1_PK8, SignatureScheme::Ed25519)?;
     let snapshot_key = PrivateKey::from_pkcs8(ED25519_2_PK8, SignatureScheme::Ed25519)?;
@@ -90,11 +123,12 @@
     //// build the targets ////
 
     let target_file: &[u8] = b"things fade, alternatives exclude";
-    let target_path = TargetPath::new("grendel".into())?;
+    let target_path = TargetPath::new("foo-bar".into())?;
     let target_description = TargetDescription::from_reader(target_file, &[HashAlgorithm::Sha256])?;
     let _ = remote.store_target(target_file, &target_path);
 
-    let target_map = hashmap!(target_path => target_description);
+    let target_map =
+        hashmap!(config.path_translator().real_to_virtual(&target_path)? => target_description);
     let targets = TargetsMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), target_map, None)?;
 
     let signed = SignedMetadata::<Json, TargetsMetadata>::new(&targets, &targets_key)?;