| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! A FileRelativePathBuf is a path which is stored in a file as being relative |
| //! to the file it's stored in. |
| //! |
| //! When a file contains paths to other files, those paths either need to be |
| //! absolute, or need to relative to some base. Consider the following tree of |
| //! directories and files: |
| //! |
| //! ``` |
| //! <root>/ |
| //! +-- resources/ |
| //! +-- foo/ |
| //! | +-- file_1.json |
| //! +-- bar/ |
| //! | +-- file_2.json |
| //! | +-- file_3.json |
| //! +-- manifest.json |
| //! ``` |
| //! |
| //! The file `manifest.json` contains the paths to each of the json files. |
| //! |
| //! If those paths are relative to itself (ie, file-relative), then they would |
| //! be: |
| //! |
| //! - foo/file_1.json |
| //! - bar/file_2.json |
| //! - bar/file_3.json |
| //! |
| //! However, for a program running in the root folder, the path to the |
| //! `manifest.json` file is `resources/manifest.json`, and the paths to the json |
| //! files it lists, when "resolved" against the path to the `manifest.json` file |
| //! are: |
| //! |
| //! - resources/foo/file_1.json |
| //! - resources/bar/file_2.json |
| //! - resources/bar/file_3.json |
| //! |
| //! This crate is meant to simplify the parsing and use of these files by |
| //! performing most of the path resolution and relativization. |
| //! |
| //! This crate offers two pieces that work in concert, an enum for the paths in |
| //! the file (to track their file-relative or resolved state), and a trait which |
| //! can be easily implemented by structs that contain these paths to handle the |
| //! conversion of the structure from one form to another. |
| //! |
| |
| use anyhow::{anyhow, Result}; |
| use camino::{Utf8Path, Utf8PathBuf}; |
| use pathdiff::diff_utf8_paths; |
| use schemars::JsonSchema; |
| use serde::{Deserialize, Serialize}; |
| |
| /// A Utf8PathBuf which can be either file-relative, or has been resolved with |
| /// the path to the containing file. |
| /// |
| /// The 'serde::Deserialize' implementation results in these path being in the |
| /// file-relative state. |
| /// |
| /// The FileRelativePathBuf has 'From' implementations that create it in the |
| /// "resolved" state when converting from path formats that are used by an |
| /// application (str, String, Utf8Path, Utf8PathBuf, etc.) |
| /// |
| /// ``` |
| /// use assembly_file_relative_path::FileRelativePathBuf; |
| /// |
| /// let path: FileRelativePathBuf = "some/path/to/file_1.json".into(); |
| /// assert_eq!( |
| /// path, |
| /// FileRelativePathBuf::Resolved("some/path/to/file_1.json".into()) |
| /// ); |
| /// |
| /// let relative = path.make_relative_to_dir("some/path")?; |
| /// assert_eq!( |
| /// relative, |
| /// FileRelativePathBuf::FileRelative("to/file_1.json".into()) |
| /// ); |
| /// ``` |
| /// |
| #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, JsonSchema)] |
| #[serde(from = "FileRelativePathBufSerializationHelper")] |
| #[serde(into = "FileRelativePathBufSerializationHelper")] |
| pub enum FileRelativePathBuf { |
| /// The path is file-relative. |
| /// |
| /// For structs that are stored in files that contain to other paths, which |
| /// are relative to the containing file, this is the serialized form of this |
| /// type. |
| /// |
| /// When first deserialized file a file (or otherwise using |
| /// 'serde::Deserialize`, this is the state that the FileRelativePathBuf |
| /// will be in. |
| /// |
| /// From this state, it can can be resolved against the path to the |
| /// containing file to make it usable by the application. |
| #[schemars(schema_with = "path_schema")] |
| FileRelative(Utf8PathBuf), |
| |
| /// The path has been 'resolved'. This is the state it's in when created directly |
| /// using From/Into. This state can be made relative from a path to a file |
| /// that will contain this path as a file-relative path. |
| #[schemars(schema_with = "path_schema")] |
| Resolved(Utf8PathBuf), |
| } |
| |
| fn path_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { |
| let mut schema: schemars::schema::SchemaObject = <String>::json_schema(gen).into(); |
| schema.format = Some("Utf8PathBuf".to_owned()); |
| schema.into() |
| } |
| |
| impl FileRelativePathBuf { |
| /// Convert the file-relative path into a resolved path that combines the |
| /// 'path_to_dir' and the file-relative path together. |
| /// |
| /// NOTE: This version takes a path to a _directory_, while the |
| /// `resolve_path_from_file()` fn takes a path to a _file_. |
| /// |
| /// ``` |
| /// let path = FileRelativePathBuf::FileRelative("foo/file_1.json".into()); |
| /// let resolved_path = path.resolve_from_dir("resources".into()); |
| /// |
| /// assert_eq!( |
| /// resolved_path, |
| /// Some(FileRelativePathBuf::Resolved("resources/foo/file_1.json")) |
| /// ); |
| ///``` |
| pub fn resolve_from_dir(self, path_to_dir: impl AsRef<Utf8Path>) -> Result<Self> { |
| match self { |
| FileRelativePathBuf::FileRelative(file_relative) => { |
| Ok(Self::Resolved(path_to_dir.as_ref().join(file_relative))) |
| } |
| passthrough @ FileRelativePathBuf::Resolved(..) => Ok(passthrough), |
| } |
| } |
| |
| /// Take a resolved path, and make it relative to a file. |
| /// |
| /// ``` |
| /// let resolved_path = FileRelativePathBuf::Resolved("resources/bar/file_2.json".into()); |
| /// let file_relative = resolved_path.make_relative_to_file("resources/manifest.json".into()); |
| /// |
| /// assert_eq!( |
| /// file_relative, |
| /// Some(FileRelativePathBuf::FileRelative("bar/file_2.json")) |
| /// ); |
| /// ``` |
| pub fn make_relative_to_file(self, path_to_file: impl AsRef<Utf8Path>) -> Result<Self> { |
| match self { |
| resolved @ Self::Resolved(..) => { |
| let dir = get_file_parent_dir(path_to_file.as_ref())?; |
| resolved.make_relative_to_dir(dir) |
| } |
| passthrough @ Self::FileRelative(..) => Ok(passthrough), |
| } |
| } |
| |
| /// Take a resolved path, and make it relative to a directory. |
| /// |
| /// ``` |
| /// let resolved_path = FileRelativePathBuf::Resolved("resources/bar/file_2.json".into()); |
| /// let file_relative = resolved_path.make_relative_to_dir("resources"); |
| /// |
| /// assert_eq!( |
| /// file_relative, |
| /// Some(FileRelativePathBuf::FileRelative("bar/file_2.json")) |
| /// ); |
| /// ``` |
| pub fn make_relative_to_dir(self, dir: impl AsRef<Utf8Path>) -> Result<Self> { |
| match self { |
| Self::Resolved(resolved) => { |
| let file_relative = diff_utf8_paths(&resolved, &dir).ok_or_else(|| { |
| anyhow!( |
| "Unable to make a path to '{}' that's relative to '{}'.", |
| resolved, |
| dir.as_ref() |
| ) |
| })?; |
| Ok(Self::FileRelative(file_relative)) |
| } |
| passthrough @ Self::FileRelative { .. } => Ok(passthrough), |
| } |
| } |
| |
| /// Use this FileRelativePathBuf as a simple Utf8PathBuf |
| pub fn as_utf8_pathbuf(&self) -> &Utf8PathBuf { |
| match self { |
| FileRelativePathBuf::FileRelative(path_in_file) => path_in_file, |
| FileRelativePathBuf::Resolved(resolved_path) => resolved_path, |
| } |
| } |
| |
| /// Convert this FileRelativePathBuf to a simple Utf8PathBuf |
| pub fn to_utf8_pathbuf(self) -> Utf8PathBuf { |
| match self { |
| FileRelativePathBuf::FileRelative(path_in_file) => path_in_file, |
| FileRelativePathBuf::Resolved(resolved_path) => resolved_path, |
| } |
| } |
| } |
| |
| /// This trait can be implemented by structs to allow them to easily resolve or |
| /// make file-relative all of their FileRelativePathBuf fields. |
| /// |
| /// The implementation of this trait is straightforward, and can be moved to a |
| /// derive macro in the future. |
| /// |
| /// ``` |
| /// struct Foo { |
| /// some_relative_path: FileRelativePathBuf, |
| /// some_flag: bool, |
| /// child: Bar, // also impl's SupportsFileRelativePaths |
| /// } |
| /// |
| /// struct Bar { |
| /// some_other_path: FileRelativePathBuf |
| /// } |
| /// |
| /// impl SupportsFileRelativePaths for Foo { |
| /// |
| /// fn resolve_paths_from_dir(self, path_to_dir: impl AsRef<Utf8Path>) -> Result<Self> { |
| /// let path_to_file = path_to_file.as_ref(); |
| /// Ok( Self { |
| /// some_relative_path: self.some_relative_path.resolve_path_from_dir(&path_to_dir)?, |
| /// child: self.child.resolve_paths_from_dir(&path_to_dir)?, |
| /// ..self |
| /// }) |
| /// } |
| /// } |
| /// |
| /// let foo = Foo{ ... } |
| /// let resolved = foo.resolve_paths_from_file("path/to/file.txt")?; |
| /// |
| /// ``` |
| pub trait SupportsFileRelativePaths { |
| /// After deserializing a file containing file-relative paths, this function |
| /// is used to convert all the FileRelativePathBufs into their resolved form. |
| /// |
| /// This is used after deserializing the struct from a file, with the path |
| /// to that file. |
| /// |
| /// ``` |
| /// struct Foo { |
| /// some_path: FileRelativePathBuf, |
| /// } |
| /// // trait impl omitted for brevity. |
| /// |
| /// let foo = Foo { |
| /// some_path: FileRelativePathBuf::FileRelative("some/file.json".into()) |
| /// }; |
| /// let foo_resolved = foo.resolve_paths_from_file("path/to/containing_file.json")?; |
| /// |
| /// assert_eq( |
| /// foo_resolved.some_path, |
| /// FileRelativePathBuf::Resolved("path/to/some/file.json".into()) |
| /// ); |
| /// ``` |
| fn resolve_paths_from_file(self, path_to_containing_file: impl AsRef<Utf8Path>) -> Result<Self> |
| where |
| Self: Sized, |
| { |
| let dir = get_file_parent_dir(path_to_containing_file.as_ref())?; |
| self.resolve_paths_from_dir(&dir) |
| } |
| |
| /// Convert all the FileRelativePathBufs in this struct (recursively) into |
| /// the Resolved state, using the path to the directory of the file that |
| /// contains file-relative paths. |
| /// |
| /// This is the function that needs to be implemented by structs. |
| /// |
| /// ``` |
| /// struct Foo { |
| /// some_path: FileRelativePathBuf, |
| /// } |
| /// // trait impl omitted for brevity. |
| /// |
| /// let foo = Foo { |
| /// some_path: FileRelativePathBuf::FileRelative("some/file.json".into()) |
| /// }; |
| /// let foo_resolved = foo.resolve_paths_from_dir("path/to".into())?; |
| /// |
| /// assert_eq( |
| /// foo_resolved.some_path, |
| /// FileRelativePathBuf::Resolved("path/to/some/file.json".into()) |
| /// ); |
| /// ``` |
| fn resolve_paths_from_dir(self, dir_path: impl AsRef<Utf8Path>) -> Result<Self> |
| where |
| Self: Sized; |
| |
| /// Convert all the FileRelativePathBufs in this struct (recursively) into |
| /// the FileRelative state, using the path to the file that will contain the |
| /// file-relative paths. |
| /// |
| /// This is used before serializing the struct to a file, with the path to |
| /// that file. |
| /// |
| /// ``` |
| /// struct Foo { |
| /// some_path: FileRelativePathBuf, |
| /// } |
| /// // trait impl omitted for brevity. |
| /// |
| /// let foo = Foo { |
| /// some_path: FileRelativePathBuf::FileRelative("some/file.json".into()) |
| /// }; |
| /// let foo_relative = foo.make_paths_relative_to_file("path/to/containing_file.json".into())?; |
| /// |
| /// assert_eq( |
| /// foo_relative.some_path, |
| /// FileRelativePathBuf::FileRelative("some/file.json".into()) |
| /// ); |
| /// ``` |
| fn make_paths_relative_to_file( |
| self, |
| path_to_containing_file: impl AsRef<Utf8Path>, |
| ) -> Result<Self> |
| where |
| Self: Sized, |
| { |
| let dir = get_file_parent_dir(path_to_containing_file.as_ref())?; |
| self.make_paths_relative_to_dir(&dir) |
| } |
| |
| /// Convert all the FileRelativePathBufs in this struct (recursively) into |
| /// the FileRelative state, using the path to the directory of the file that |
| /// will contain the file-relative paths. |
| /// |
| /// This is the function that needs to be implemented by structs. |
| /// |
| /// ``` |
| /// struct Foo { |
| /// some_path: FileRelativePathBuf, |
| /// } |
| /// // trait impl omitted for brevity. |
| /// |
| /// let foo = Foo { |
| /// some_path: FileRelativePathBuf::FileRelative("some/file.json".into()) |
| /// }; |
| /// let foo_relative = foo.make_paths_relative_to_file("path/to".into())?; |
| /// |
| /// assert_eq( |
| /// foo_relative.some_path, |
| /// FileRelativePathBuf::FileRelative("some/file.json".into()) |
| /// ); |
| /// ``` |
| fn make_paths_relative_to_dir( |
| self, |
| path_to_containing_dir: impl AsRef<Utf8Path>, |
| ) -> Result<Self> |
| where |
| Self: Sized; |
| } |
| |
| /// A helper function to get the parent of a Utf8Path, or return an error |
| /// that the given path doesn't have a parent. |
| fn get_file_parent_dir(file_path: impl AsRef<Utf8Path>) -> Result<Utf8PathBuf> { |
| file_path.as_ref().parent().map(Into::into).ok_or_else(|| { |
| anyhow!("The given file path does not have a parent: {}", file_path.as_ref()) |
| }) |
| } |
| |
| // Implement SupportsFileRelativePaths for FileRelativePathBuf so there is |
| // has an implementation that satisfies Option<T: SupportsFileRelativePaths> for |
| // Option<FileRelativePathBuf> |
| impl SupportsFileRelativePaths for FileRelativePathBuf { |
| fn resolve_paths_from_dir(self, dir_path: impl AsRef<Utf8Path>) -> Result<Self> |
| where |
| Self: Sized, |
| { |
| self.resolve_from_dir(dir_path) |
| } |
| |
| fn make_paths_relative_to_dir( |
| self, |
| path_to_containing_dir: impl AsRef<Utf8Path>, |
| ) -> Result<Self> |
| where |
| Self: Sized, |
| { |
| self.make_relative_to_dir(path_to_containing_dir) |
| } |
| } |
| |
| // |
| // Serialization / Deserialization implementations for FileRelativePathBuf |
| // |
| |
| /// This struct is used by serde to perform the Serialization and Deserialization |
| /// of the FileRelativePathBuf. |
| #[derive(Debug, Clone, Deserialize, Serialize)] |
| struct FileRelativePathBufSerializationHelper(Utf8PathBuf); |
| |
| impl From<FileRelativePathBufSerializationHelper> for FileRelativePathBuf { |
| fn from(value: FileRelativePathBufSerializationHelper) -> Self { |
| FileRelativePathBuf::FileRelative(value.0) |
| } |
| } |
| |
| impl Into<FileRelativePathBufSerializationHelper> for FileRelativePathBuf { |
| fn into(self) -> FileRelativePathBufSerializationHelper { |
| FileRelativePathBufSerializationHelper(self.into()) |
| } |
| } |
| |
| // |
| // Conversion implementations for FileRelativePathBuf |
| // |
| |
| // The FileRelativePathBuf can be converted into a standard Utf8PathBuf in either |
| // form. |
| impl From<FileRelativePathBuf> for Utf8PathBuf { |
| fn from(value: FileRelativePathBuf) -> Self { |
| value.to_utf8_pathbuf() |
| } |
| } |
| |
| impl AsRef<Utf8Path> for FileRelativePathBuf { |
| fn as_ref(&self) -> &Utf8Path { |
| self.as_utf8_pathbuf() |
| } |
| } |
| |
| impl AsRef<std::path::Path> for FileRelativePathBuf { |
| fn as_ref(&self) -> &std::path::Path { |
| self.as_utf8_pathbuf().as_std_path() |
| } |
| } |
| |
| // Creating a FileRelativePathBuf from a Utf8PathBuf or a string of some kind |
| // will always create it in the resolved state (since this is likely the format |
| // that those paths will be in). |
| impl From<Utf8PathBuf> for FileRelativePathBuf { |
| fn from(value: Utf8PathBuf) -> Self { |
| Self::Resolved(value) |
| } |
| } |
| |
| impl From<String> for FileRelativePathBuf { |
| fn from(string: String) -> FileRelativePathBuf { |
| FileRelativePathBuf::Resolved(string.into()) |
| } |
| } |
| |
| impl std::str::FromStr for FileRelativePathBuf { |
| type Err = std::convert::Infallible; |
| |
| fn from_str(s: &str) -> Result<Self, Self::Err> { |
| Ok(FileRelativePathBuf::Resolved(s.into())) |
| } |
| } |
| |
| impl<T: ?Sized + AsRef<str>> From<&T> for FileRelativePathBuf { |
| fn from(s: &T) -> FileRelativePathBuf { |
| FileRelativePathBuf::from(s.as_ref().to_owned()) |
| } |
| } |
| |
| impl std::fmt::Display for FileRelativePathBuf { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| AsRef::<Utf8Path>::as_ref(self).fmt(f) |
| } |
| } |
| |
| // |
| // Trait implementations for various containers of FileRelativePathBuf |
| // |
| |
| impl<T: SupportsFileRelativePaths> SupportsFileRelativePaths for Vec<T> { |
| fn resolve_paths_from_dir(self, dir_path: impl AsRef<Utf8Path>) -> Result<Self> { |
| self.into_iter().map(|p| p.resolve_paths_from_dir(&dir_path)).collect() |
| } |
| |
| fn make_paths_relative_to_dir( |
| self, |
| path_to_containing_dir: impl AsRef<Utf8Path>, |
| ) -> Result<Self> { |
| self.into_iter().map(|p| p.make_paths_relative_to_dir(&path_to_containing_dir)).collect() |
| } |
| } |
| |
| impl<T: SupportsFileRelativePaths> SupportsFileRelativePaths for Option<T> { |
| fn resolve_paths_from_dir(self, dir_path: impl AsRef<Utf8Path>) -> Result<Self> { |
| self.map(|s| s.resolve_paths_from_dir(dir_path)).transpose() |
| } |
| |
| fn make_paths_relative_to_dir( |
| self, |
| path_to_containing_dir: impl AsRef<Utf8Path>, |
| ) -> Result<Self> { |
| self.map(|s| s.make_paths_relative_to_dir(path_to_containing_dir)).transpose() |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use std::str::FromStr; |
| |
| #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] |
| struct SimpleStruct { |
| flag: bool, |
| path: FileRelativePathBuf, |
| } |
| |
| impl SupportsFileRelativePaths for SimpleStruct { |
| fn resolve_paths_from_dir(self, path_to_dir: impl AsRef<Utf8Path>) -> Result<Self> { |
| Ok(Self { path: self.path.resolve_from_dir(path_to_dir)?, ..self }) |
| } |
| |
| fn make_paths_relative_to_dir( |
| self, |
| path_to_containing_dir: impl AsRef<Utf8Path>, |
| ) -> Result<Self> { |
| Ok(Self { path: self.path.make_relative_to_dir(path_to_containing_dir)?, ..self }) |
| } |
| } |
| |
| #[test] |
| fn test_standard_usage_simple_struct() { |
| let json = serde_json::json!({ |
| "flag": true, |
| "path": "foo/file_1.json" |
| }); |
| |
| // The parsed file uses "file-relative" paths |
| let parsed: SimpleStruct = serde_json::from_value(json).unwrap(); |
| assert_eq!( |
| &parsed, |
| &SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::FileRelative("foo/file_1.json".into()) |
| } |
| ); |
| |
| // After resolving, they will be "resolved" paths |
| let resolved = parsed.resolve_paths_from_file("resources/manifest.json").unwrap(); |
| assert_eq!( |
| resolved, |
| SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::Resolved("resources/foo/file_1.json".into()) |
| } |
| ); |
| |
| // When created from paths/strings, they will start as "resolved" |
| let created = SimpleStruct { flag: false, path: "resources/foo/file_1.json".into() }; |
| assert_eq!( |
| &created, |
| &SimpleStruct { |
| flag: false, |
| path: FileRelativePathBuf::Resolved("resources/foo/file_1.json".into()) |
| } |
| ); |
| |
| // And then can be made relative |
| let relative = created.make_paths_relative_to_file("resources/manifest.json").unwrap(); |
| assert_eq!( |
| relative, |
| SimpleStruct { |
| flag: false, |
| path: FileRelativePathBuf::FileRelative("foo/file_1.json".into()) |
| } |
| ); |
| |
| // The re-serialized value should be the same as it originally was. |
| let serialized = serde_json::to_value(relative).unwrap(); |
| assert_eq!( |
| serialized, |
| serde_json::json!({ |
| "flag": false, |
| "path": "foo/file_1.json" |
| }) |
| ) |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] |
| struct ParentStruct { |
| name: String, |
| some_path: FileRelativePathBuf, |
| child: SimpleStruct, |
| } |
| |
| impl SupportsFileRelativePaths for ParentStruct { |
| fn resolve_paths_from_dir(self, path_to_dir: impl AsRef<Utf8Path>) -> Result<Self> { |
| Ok(Self { |
| // Convert the path in this struct |
| some_path: self.some_path.resolve_from_dir(&path_to_dir)?, |
| |
| // Convert the fields that implement this trait |
| child: self.child.resolve_paths_from_dir(&path_to_dir)?, |
| |
| // and forward the rest of the fields |
| ..self |
| }) |
| } |
| |
| fn make_paths_relative_to_dir(self, path_to_dir: impl AsRef<Utf8Path>) -> Result<Self> { |
| Ok(Self { |
| // Convert the path in this struct |
| some_path: self.some_path.make_relative_to_dir(&path_to_dir)?, |
| |
| // Convert the fields that implement this trait |
| child: self.child.make_paths_relative_to_dir(&path_to_dir)?, |
| |
| // and forward the rest of the fields |
| ..self |
| }) |
| } |
| } |
| |
| #[test] |
| fn test_standard_usage_nested_struct() { |
| let json = serde_json::json!({ |
| "name": "A name", |
| "some_path": "bar/file_2.json", |
| "child": { |
| "flag": true, |
| "path": "foo/file_1.json" |
| } |
| }); |
| |
| // The parsed file uses "file-relative" paths |
| let parsed: ParentStruct = serde_json::from_value(json).unwrap(); |
| assert_eq!( |
| &parsed, |
| &ParentStruct { |
| name: "A name".into(), |
| some_path: FileRelativePathBuf::FileRelative("bar/file_2.json".into()), |
| child: SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::FileRelative("foo/file_1.json".into()) |
| } |
| } |
| ); |
| |
| // And then can be resolved: |
| let resolved = parsed.resolve_paths_from_file("resources/manifest.json").unwrap(); |
| assert_eq!( |
| resolved, |
| ParentStruct { |
| name: "A name".into(), |
| some_path: FileRelativePathBuf::Resolved("resources/bar/file_2.json".into()), |
| child: SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::Resolved("resources/foo/file_1.json".into()) |
| } |
| } |
| ); |
| |
| // Create a new one (say after copying): |
| let created = ParentStruct { |
| name: "A name".into(), |
| some_path: "some/new/location/bar/file_2.json".into(), |
| child: SimpleStruct { flag: true, path: "some/new/location/foo/file_1.json".into() }, |
| }; |
| assert_eq!( |
| created.some_path, |
| FileRelativePathBuf::Resolved("some/new/location/bar/file_2.json".into()) |
| ); |
| assert_eq!( |
| created.child.path, |
| FileRelativePathBuf::Resolved("some/new/location/foo/file_1.json".into()) |
| ); |
| |
| // And make relative again: |
| let relative = |
| created.make_paths_relative_to_file("some/new/location/outputs.json").unwrap(); |
| assert_eq!( |
| relative, |
| ParentStruct { |
| name: "A name".into(), |
| some_path: FileRelativePathBuf::FileRelative("bar/file_2.json".into()), |
| child: SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::FileRelative("foo/file_1.json".into()) |
| } |
| } |
| ); |
| |
| // And then serialize: |
| let serialzed = serde_json::to_value(relative).unwrap(); |
| assert_eq!( |
| serialzed, |
| serde_json::json!({ |
| "name": "A name", |
| "some_path": "bar/file_2.json", |
| "child": { |
| "flag": true, |
| "path": "foo/file_1.json" |
| } |
| }) |
| ); |
| } |
| |
| #[derive(Deserialize)] |
| struct StructWithVec { |
| paths: Vec<FileRelativePathBuf>, |
| } |
| |
| impl SupportsFileRelativePaths for StructWithVec { |
| fn resolve_paths_from_dir(self, dir_path: impl AsRef<Utf8Path>) -> Result<Self> |
| where |
| Self: Sized, |
| { |
| Ok(Self { paths: self.paths.resolve_paths_from_dir(dir_path)? }) |
| } |
| |
| fn make_paths_relative_to_dir( |
| self, |
| path_to_containing_dir: impl AsRef<Utf8Path>, |
| ) -> Result<Self> |
| where |
| Self: Sized, |
| { |
| Ok(Self { paths: self.paths.make_paths_relative_to_dir(path_to_containing_dir)? }) |
| } |
| } |
| |
| #[test] |
| fn test_trait_impl_vec_resolve() { |
| let json = serde_json::json!({ |
| "paths": [ |
| "file_1", |
| "file_2", |
| "file_3" |
| ] |
| }); |
| |
| let parsed: StructWithVec = serde_json::from_value(json).unwrap(); |
| assert_eq!( |
| &parsed.paths, |
| &vec![ |
| FileRelativePathBuf::FileRelative("file_1".into()), |
| FileRelativePathBuf::FileRelative("file_2".into()), |
| FileRelativePathBuf::FileRelative("file_3".into()), |
| ] |
| ); |
| |
| let resolved = parsed.resolve_paths_from_file("some/manifest").unwrap(); |
| assert_eq!( |
| &resolved.paths, |
| &vec![ |
| FileRelativePathBuf::Resolved("some/file_1".into()), |
| FileRelativePathBuf::Resolved("some/file_2".into()), |
| FileRelativePathBuf::Resolved("some/file_3".into()), |
| ] |
| ); |
| } |
| |
| #[test] |
| fn test_trait_impl_vec_make_relative() { |
| let created = StructWithVec { |
| paths: vec!["some/file_1".into(), "some/file_2".into(), "some/file_3".into()], |
| }; |
| |
| let relative = created.make_paths_relative_to_file("some/other_manifest").unwrap(); |
| assert_eq!( |
| &relative.paths, |
| &vec![ |
| FileRelativePathBuf::FileRelative("file_1".into()), |
| FileRelativePathBuf::FileRelative("file_2".into()), |
| FileRelativePathBuf::FileRelative("file_3".into()), |
| ] |
| ); |
| } |
| |
| #[test] |
| fn test_trait_impl_option_resolve() { |
| let option = Some(SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::FileRelative("a/file.json".into()), |
| }); |
| let resolved = option.resolve_paths_from_file("some/manifest.list").unwrap(); |
| assert_eq!( |
| resolved, |
| Some(SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::Resolved("some/a/file.json".into()) |
| }) |
| ); |
| } |
| |
| #[test] |
| fn test_trait_impl_option_make_relative() { |
| let option = Some(SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::Resolved("some/a/file.json".into()), |
| }); |
| let resolved = option.make_paths_relative_to_file("some/manifest.list").unwrap(); |
| assert_eq!( |
| resolved, |
| Some(SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::FileRelative("a/file.json".into()) |
| }) |
| ); |
| } |
| |
| #[test] |
| fn test_trait_resolve_usage() { |
| let parsed = SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::FileRelative("foo/file_1.json".into()), |
| }; |
| let resolved = parsed.resolve_paths_from_file("resources/manifest.json").unwrap(); |
| assert_eq!( |
| resolved, |
| SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::Resolved("resources/foo/file_1.json".into()) |
| } |
| ); |
| } |
| |
| #[test] |
| fn test_trait_make_relative_usage() { |
| let original = SimpleStruct { flag: true, path: "resources/foo/file_1.json".into() }; |
| let relative = original.make_paths_relative_to_file("resources/manifest.json").unwrap(); |
| assert_eq!( |
| relative, |
| SimpleStruct { |
| flag: true, |
| path: FileRelativePathBuf::FileRelative("foo/file_1.json".into()) |
| } |
| ) |
| } |
| |
| #[test] |
| fn test_field_from_str() { |
| // Validate that when creating from a string, it's always a 'resolved' path. |
| let created = FileRelativePathBuf::from_str("resources/foo/file_2.json").unwrap(); |
| assert_eq!(created, FileRelativePathBuf::Resolved("resources/foo/file_2.json".into())); |
| } |
| |
| #[test] |
| fn test_field_from_string() { |
| let created = FileRelativePathBuf::from(&String::from("resources/foo/file_2.json")); |
| assert_eq!(created, FileRelativePathBuf::Resolved("resources/foo/file_2.json".into())) |
| } |
| |
| #[test] |
| fn test_field_resolve_from_dir() { |
| let parsed = FileRelativePathBuf::FileRelative("foo/file_2.json".into()); |
| let resolved = parsed.resolve_from_dir("resources").unwrap(); |
| assert_eq!(resolved, FileRelativePathBuf::Resolved("resources/foo/file_2.json".into())); |
| } |
| |
| #[test] |
| fn test_field_make_relative_to_file() { |
| let original = FileRelativePathBuf::from("foo/bar/baz.json"); |
| let relative = original.make_relative_to_file("foo/file.json").unwrap(); |
| assert_eq!(relative, FileRelativePathBuf::FileRelative("bar/baz.json".into())) |
| } |
| |
| #[test] |
| fn test_field_make_relative_to_dir() { |
| let original = FileRelativePathBuf::from("foo/bar/baz.json"); |
| let relative = original.make_relative_to_dir("foo").unwrap(); |
| assert_eq!(relative, FileRelativePathBuf::FileRelative("bar/baz.json".into())) |
| } |
| |
| #[test] |
| fn test_display() { |
| let original = FileRelativePathBuf::from("foo/bar/baz.json"); |
| assert_eq!(format!("{}", original), "foo/bar/baz.json"); |
| |
| let relative = original.make_relative_to_dir("foo").unwrap(); |
| assert_eq!(format!("{}", relative), "bar/baz.json"); |
| } |
| } |