1+ use std:: collections:: HashMap ;
12use std:: path:: { Path , PathBuf } ;
23use std:: time:: Duration ;
34
@@ -12,16 +13,29 @@ use crate::prelude::{Error, JsonValueStream, LinkResolver as LinkResolverTrait};
1213pub struct FileLinkResolver {
1314 base_dir : Option < PathBuf > ,
1415 timeout : Duration ,
16+ // This is a hashmap that maps the alias name to the path of the file that is aliased
17+ aliases : HashMap < String , PathBuf > ,
18+ }
19+
20+ impl Default for FileLinkResolver {
21+ fn default ( ) -> Self {
22+ Self {
23+ base_dir : None ,
24+ timeout : Duration :: from_secs ( 30 ) ,
25+ aliases : HashMap :: new ( ) ,
26+ }
27+ }
1528}
1629
1730impl FileLinkResolver {
1831 /// Create a new FileLinkResolver
1932 ///
2033 /// All paths are treated as absolute paths.
21- pub fn new ( ) -> Self {
34+ pub fn new ( base_dir : Option < PathBuf > , aliases : HashMap < String , PathBuf > ) -> Self {
2235 Self {
23- base_dir : None ,
36+ base_dir : base_dir ,
2437 timeout : Duration :: from_secs ( 30 ) ,
38+ aliases,
2539 }
2640 }
2741
@@ -33,18 +47,59 @@ impl FileLinkResolver {
3347 Self {
3448 base_dir : Some ( base_dir. as_ref ( ) . to_owned ( ) ) ,
3549 timeout : Duration :: from_secs ( 30 ) ,
50+ aliases : HashMap :: new ( ) ,
3651 }
3752 }
3853
3954 fn resolve_path ( & self , link : & str ) -> PathBuf {
4055 let path = Path :: new ( link) ;
4156
57+ // If the path is an alias, use the aliased path
58+ if let Some ( aliased) = self . aliases . get ( link) {
59+ return aliased. clone ( ) ;
60+ }
61+
4262 // Return the path as is if base_dir is None, or join with base_dir if present.
4363 // if "link" is an absolute path, join will simply return that path.
4464 self . base_dir
4565 . as_ref ( )
4666 . map_or_else ( || path. to_owned ( ) , |base_dir| base_dir. join ( link) )
4767 }
68+
69+ /// This method creates a new resolver that is scoped to a specific subgraph
70+ /// It will set the base directory to the parent directory of the manifest path
71+ /// This is required because paths mentioned in the subgraph manifest are relative paths
72+ /// and we need a new resolver with the right base directory for the specific subgraph
73+ fn clone_for_manifest ( & self , manifest_path_str : & str ) -> Result < Self , Error > {
74+ let mut resolver = self . clone ( ) ;
75+
76+ // Create a path to the manifest based on the current resolver's
77+ // base directory or default to using the deployment string as path
78+ // If the deployment string is an alias, use the aliased path
79+ let manifest_path = if let Some ( aliased) = self . aliases . get ( & manifest_path_str. to_string ( ) )
80+ {
81+ aliased. clone ( )
82+ } else {
83+ match & resolver. base_dir {
84+ Some ( dir) => dir. join ( & manifest_path_str) ,
85+ None => PathBuf :: from ( manifest_path_str) ,
86+ }
87+ } ;
88+
89+ let canonical_manifest_path = manifest_path
90+ . canonicalize ( )
91+ . map_err ( |e| Error :: from ( anyhow ! ( "Failed to canonicalize manifest path: {}" , e) ) ) ?;
92+
93+ // The manifest path is the path of the subgraph manifest file in the build directory
94+ // We use the parent directory as the base directory for the new resolver
95+ let base_dir = canonical_manifest_path
96+ . parent ( )
97+ . ok_or_else ( || Error :: from ( anyhow ! ( "Manifest path has no parent directory" ) ) ) ?
98+ . to_path_buf ( ) ;
99+
100+ resolver. base_dir = Some ( base_dir) ;
101+ Ok ( resolver)
102+ }
48103}
49104
50105pub fn remove_prefix ( link : & str ) -> & str {
@@ -86,6 +141,10 @@ impl LinkResolverTrait for FileLinkResolver {
86141 }
87142 }
88143
144+ fn for_manifest ( & self , manifest_path : & str ) -> Result < Box < dyn LinkResolverTrait > , Error > {
145+ Ok ( Box :: new ( self . clone_for_manifest ( manifest_path) ?) )
146+ }
147+
89148 async fn get_block ( & self , _logger : & Logger , _link : & Link ) -> Result < Vec < u8 > , Error > {
90149 Err ( anyhow ! ( "get_block is not implemented for FileLinkResolver" ) . into ( ) )
91150 }
@@ -117,7 +176,7 @@ mod tests {
117176 file. write_all ( test_content) . unwrap ( ) ;
118177
119178 // Create a resolver without a base directory
120- let resolver = FileLinkResolver :: new ( ) ;
179+ let resolver = FileLinkResolver :: default ( ) ;
121180 let logger = slog:: Logger :: root ( slog:: Discard , slog:: o!( ) ) ;
122181
123182 // Test valid path resolution
@@ -185,4 +244,64 @@ mod tests {
185244 let _ = fs:: remove_file ( test_file_path) ;
186245 let _ = fs:: remove_dir ( temp_dir) ;
187246 }
247+
248+ #[ tokio:: test]
249+ async fn test_file_resolver_with_aliases ( ) {
250+ // Create a temporary directory for test files
251+ let temp_dir = env:: temp_dir ( ) . join ( "file_resolver_test_aliases" ) ;
252+ let _ = fs:: create_dir_all ( & temp_dir) ;
253+
254+ // Create two test files with different content
255+ let test_file1_path = temp_dir. join ( "file.txt" ) ;
256+ let test_content1 = b"This is the file content" ;
257+ let mut file1 = fs:: File :: create ( & test_file1_path) . unwrap ( ) ;
258+ file1. write_all ( test_content1) . unwrap ( ) ;
259+
260+ let test_file2_path = temp_dir. join ( "another_file.txt" ) ;
261+ let test_content2 = b"This is another file content" ;
262+ let mut file2 = fs:: File :: create ( & test_file2_path) . unwrap ( ) ;
263+ file2. write_all ( test_content2) . unwrap ( ) ;
264+
265+ // Create aliases mapping
266+ let mut aliases = HashMap :: new ( ) ;
267+ aliases. insert ( "alias1" . to_string ( ) , test_file1_path. clone ( ) ) ;
268+ aliases. insert ( "alias2" . to_string ( ) , test_file2_path. clone ( ) ) ;
269+ aliases. insert ( "deployment-id" . to_string ( ) , test_file1_path. clone ( ) ) ;
270+
271+ // Create resolver with aliases
272+ let resolver = FileLinkResolver :: new ( Some ( temp_dir. clone ( ) ) , aliases) ;
273+ let logger = slog:: Logger :: root ( slog:: Discard , slog:: o!( ) ) ;
274+
275+ // Test resolving by aliases
276+ let link1 = Link {
277+ link : "alias1" . to_string ( ) ,
278+ } ;
279+ let result1 = resolver. cat ( & logger, & link1) . await . unwrap ( ) ;
280+ assert_eq ! ( result1, test_content1) ;
281+
282+ let link2 = Link {
283+ link : "alias2" . to_string ( ) ,
284+ } ;
285+ let result2 = resolver. cat ( & logger, & link2) . await . unwrap ( ) ;
286+ assert_eq ! ( result2, test_content2) ;
287+
288+ // Test that the alias works in for_deployment as well
289+ let deployment_resolver = resolver. clone_for_manifest ( "deployment-id" ) . unwrap ( ) ;
290+
291+ let expected_dir = test_file1_path. parent ( ) . unwrap ( ) ;
292+ let deployment_base_dir = deployment_resolver. base_dir . clone ( ) . unwrap ( ) ;
293+
294+ let canonical_expected_dir = expected_dir. canonicalize ( ) . unwrap ( ) ;
295+ let canonical_deployment_dir = deployment_base_dir. canonicalize ( ) . unwrap ( ) ;
296+
297+ assert_eq ! (
298+ canonical_deployment_dir, canonical_expected_dir,
299+ "Build directory paths don't match"
300+ ) ;
301+
302+ // Clean up
303+ let _ = fs:: remove_file ( test_file1_path) ;
304+ let _ = fs:: remove_file ( test_file2_path) ;
305+ let _ = fs:: remove_dir ( temp_dir) ;
306+ }
188307}
0 commit comments