forc_pkg/source/reg/
file_location.rs

1use serde::{Deserialize, Serialize};
2use std::{fmt::Display, path::PathBuf};
3
4/// Number of levels of nesting to use for file locations.
5const NESTING_LEVELS: usize = 2;
6
7#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
8pub enum Namespace {
9    /// Flat namespace means no sub-namespace with different domains.
10    /// Location calculator won't be adding anything specific for this to the
11    /// file location.
12    Flat,
13    /// Domain namespace means we have custom namespaces and first component of
14    /// the file location of the index file will be the domain of the namespace.
15    /// Which means in the index repository all namespaced packages will first
16    /// have the namespace in their paths.
17    Domain(String),
18}
19
20impl Display for Namespace {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        match self {
23            Namespace::Flat => write!(f, ""),
24            Namespace::Domain(s) => write!(f, "{s}"),
25        }
26    }
27}
28
29/// Calculates the exact file location from the root of the namespace repo.
30/// If the configuration includes a namespace, it will be the first part of
31/// the path followed by chunks.
32pub fn location_from_root(chunk_size: usize, namespace: &Namespace, package_name: &str) -> PathBuf {
33    let mut path = PathBuf::new();
34
35    // Add domain to path if namespace is 'Domain' and it is not empty
36    // otherwise skip.
37    match namespace {
38        Namespace::Domain(domain) if !domain.is_empty() => {
39            path.push(domain);
40        }
41        _ => {}
42    }
43
44    // If chunking is disabled we do not have any folder in the index.
45    if chunk_size == 0 {
46        path.push(package_name);
47        return path;
48    }
49
50    let char_count = chunk_size * NESTING_LEVELS;
51    let to_be_chunked_section = package_name
52        .chars()
53        .enumerate()
54        .take_while(|(index, _)| *index < char_count)
55        .map(|(_, ch)| ch);
56
57    let chars: Vec<char> = to_be_chunked_section.collect();
58    for chunk in chars.chunks(chunk_size) {
59        let chunk_str: String = chunk.iter().collect();
60        path.push(chunk_str);
61    }
62
63    path.push(package_name);
64    path
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::source::reg::index_file::PackageEntry;
71    use semver::Version;
72    use std::path::Path;
73
74    fn create_package_entry(name: &str) -> PackageEntry {
75        let name = name.to_string();
76        let version = Version::new(1, 0, 0);
77        let source_cid = "QmHash".to_string();
78        let abi_cid = None;
79        let dependencies = vec![];
80        let yanked = false;
81        PackageEntry::new(name, version, source_cid, abi_cid, dependencies, yanked)
82    }
83
84    #[test]
85    fn test_flat_namespace_with_small_package() {
86        let chunk_size = 2;
87        let namespace = Namespace::Flat;
88        let entry = create_package_entry("ab");
89
90        let path = location_from_root(chunk_size, &namespace, entry.name());
91
92        assert_eq!(path, Path::new("ab").join("ab"));
93    }
94
95    #[test]
96    fn test_flat_namespace_with_regular_package() {
97        let chunk_size = 2;
98        let namespace = Namespace::Flat;
99        let entry = create_package_entry("foobar");
100
101        let path = location_from_root(chunk_size, &namespace, entry.name());
102
103        // Should produce: fo/ob/foobar
104        assert_eq!(path, Path::new("fo").join("ob").join("foobar"));
105    }
106
107    #[test]
108    fn test_domain_namespace() {
109        let chunk_size = 2;
110        let namespace = Namespace::Domain("example".to_string());
111        let entry = create_package_entry("foobar");
112
113        let path = location_from_root(chunk_size, &namespace, entry.name());
114
115        // Should produce: example/fo/ob/foobar
116        assert_eq!(
117            path,
118            Path::new("example").join("fo").join("ob").join("foobar")
119        );
120    }
121
122    #[test]
123    fn test_odd_length_package_name() {
124        let chunk_size = 2;
125        let namespace = Namespace::Flat;
126        let entry = create_package_entry("hello");
127
128        let path = location_from_root(chunk_size, &namespace, entry.name());
129
130        // Should produce: he/ll/hello
131        assert_eq!(path, Path::new("he").join("ll").join("hello"));
132    }
133
134    #[test]
135    fn test_larger_chunking_size() {
136        let chunk_size = 3;
137        let namespace = Namespace::Flat;
138        let entry = create_package_entry("fibonacci");
139
140        let path = location_from_root(chunk_size, &namespace, entry.name());
141
142        // Should produce: fib/ona/fibonacci
143        assert_eq!(path, Path::new("fib").join("ona").join("fibonacci"));
144    }
145
146    #[test]
147    fn test_chunking_size_larger_than_name() {
148        let chunk_size = 10;
149        let namespace = Namespace::Flat;
150        let entry = create_package_entry("small");
151
152        let path = location_from_root(chunk_size, &namespace, entry.name());
153
154        // Should produce: small/small
155        assert_eq!(path, Path::new("small").join("small"));
156    }
157
158    #[test]
159    fn test_unicode_package_name() {
160        let chunk_size = 2;
161        let namespace = Namespace::Flat;
162        let entry = create_package_entry("héllo");
163
164        let path = location_from_root(chunk_size, &namespace, entry.name());
165
166        // Should produce: hé/ll/héllo
167        assert_eq!(path, Path::new("hé").join("ll").join("héllo"));
168    }
169
170    #[test]
171    fn test_empty_package_name() {
172        let chunk_size = 0;
173        let namespace = Namespace::Flat;
174        let entry = create_package_entry("");
175
176        let path = location_from_root(chunk_size, &namespace, entry.name());
177
178        // Should just produce: ""
179        assert_eq!(path, Path::new(""));
180    }
181
182    #[test]
183    fn test_chunking_size_zero() {
184        let chunk_size = 0;
185        let namespace = Namespace::Flat;
186        let entry = create_package_entry("package");
187
188        let path = location_from_root(chunk_size, &namespace, entry.name());
189
190        // Should just produce: package
191        assert_eq!(path, Path::new("package"));
192    }
193}