-
Notifications
You must be signed in to change notification settings - Fork 1k
gnd: Support multiple subgraphs, grafting, subgraph composition in dev mode #6000
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ec4b36d
4af0e6d
b1e4a0c
b7dcdca
4263ebc
1616bf8
3915686
32b7163
317b381
fdedb23
9a90dff
3012224
72e0da4
8e51bf2
747f7a9
2eccf06
d99396f
80e7139
b349d60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
use std::collections::HashMap; | ||
use std::path::{Path, PathBuf}; | ||
use std::time::Duration; | ||
|
||
|
@@ -12,16 +13,29 @@ use crate::prelude::{Error, JsonValueStream, LinkResolver as LinkResolverTrait}; | |
pub struct FileLinkResolver { | ||
base_dir: Option<PathBuf>, | ||
timeout: Duration, | ||
// This is a hashmap that maps the alias name to the path of the file that is aliased | ||
aliases: HashMap<String, PathBuf>, | ||
} | ||
|
||
impl Default for FileLinkResolver { | ||
fn default() -> Self { | ||
Self { | ||
base_dir: None, | ||
timeout: Duration::from_secs(30), | ||
aliases: HashMap::new(), | ||
} | ||
} | ||
} | ||
|
||
impl FileLinkResolver { | ||
/// Create a new FileLinkResolver | ||
/// | ||
/// All paths are treated as absolute paths. | ||
pub fn new() -> Self { | ||
pub fn new(base_dir: Option<PathBuf>, aliases: HashMap<String, PathBuf>) -> Self { | ||
Self { | ||
base_dir: None, | ||
base_dir: base_dir, | ||
timeout: Duration::from_secs(30), | ||
aliases, | ||
} | ||
} | ||
|
||
|
@@ -33,18 +47,59 @@ impl FileLinkResolver { | |
Self { | ||
base_dir: Some(base_dir.as_ref().to_owned()), | ||
timeout: Duration::from_secs(30), | ||
aliases: HashMap::new(), | ||
} | ||
} | ||
|
||
fn resolve_path(&self, link: &str) -> PathBuf { | ||
let path = Path::new(link); | ||
|
||
// If the path is an alias, use the aliased path | ||
if let Some(aliased) = self.aliases.get(link) { | ||
return aliased.clone(); | ||
} | ||
|
||
// Return the path as is if base_dir is None, or join with base_dir if present. | ||
// if "link" is an absolute path, join will simply return that path. | ||
self.base_dir | ||
.as_ref() | ||
.map_or_else(|| path.to_owned(), |base_dir| base_dir.join(link)) | ||
} | ||
|
||
/// This method creates a new resolver that is scoped to a specific subgraph | ||
/// It will set the base directory to the parent directory of the manifest path | ||
/// This is required because paths mentioned in the subgraph manifest are relative paths | ||
/// and we need a new resolver with the right base directory for the specific subgraph | ||
incrypto32 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fn clone_for_manifest(&self, manifest_path_str: &str) -> Result<Self, Error> { | ||
let mut resolver = self.clone(); | ||
|
||
// Create a path to the manifest based on the current resolver's | ||
// base directory or default to using the deployment string as path | ||
// If the deployment string is an alias, use the aliased path | ||
let manifest_path = if let Some(aliased) = self.aliases.get(&manifest_path_str.to_string()) | ||
{ | ||
aliased.clone() | ||
} else { | ||
match &resolver.base_dir { | ||
Some(dir) => dir.join(&manifest_path_str), | ||
None => PathBuf::from(manifest_path_str), | ||
} | ||
}; | ||
|
||
let canonical_manifest_path = manifest_path | ||
.canonicalize() | ||
.map_err(|e| Error::from(anyhow!("Failed to canonicalize manifest path: {}", e)))?; | ||
|
||
// The manifest path is the path of the subgraph manifest file in the build directory | ||
// We use the parent directory as the base directory for the new resolver | ||
let base_dir = canonical_manifest_path | ||
.parent() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am a bit confused now, isn't this basically There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. deployment_str can be something like When deployment_str is an absolute path its simply the directory in which the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't |
||
.ok_or_else(|| Error::from(anyhow!("Manifest path has no parent directory")))? | ||
.to_path_buf(); | ||
|
||
resolver.base_dir = Some(base_dir); | ||
Ok(resolver) | ||
} | ||
} | ||
|
||
pub fn remove_prefix(link: &str) -> &str { | ||
|
@@ -86,6 +141,10 @@ impl LinkResolverTrait for FileLinkResolver { | |
} | ||
} | ||
|
||
fn for_manifest(&self, manifest_path: &str) -> Result<Box<dyn LinkResolverTrait>, Error> { | ||
Ok(Box::new(self.clone_for_manifest(manifest_path)?)) | ||
} | ||
|
||
async fn get_block(&self, _logger: &Logger, _link: &Link) -> Result<Vec<u8>, Error> { | ||
Err(anyhow!("get_block is not implemented for FileLinkResolver").into()) | ||
} | ||
|
@@ -117,7 +176,7 @@ mod tests { | |
file.write_all(test_content).unwrap(); | ||
|
||
// Create a resolver without a base directory | ||
let resolver = FileLinkResolver::new(); | ||
let resolver = FileLinkResolver::default(); | ||
let logger = slog::Logger::root(slog::Discard, slog::o!()); | ||
|
||
// Test valid path resolution | ||
|
@@ -185,4 +244,64 @@ mod tests { | |
let _ = fs::remove_file(test_file_path); | ||
let _ = fs::remove_dir(temp_dir); | ||
} | ||
|
||
#[tokio::test] | ||
async fn test_file_resolver_with_aliases() { | ||
// Create a temporary directory for test files | ||
let temp_dir = env::temp_dir().join("file_resolver_test_aliases"); | ||
let _ = fs::create_dir_all(&temp_dir); | ||
|
||
// Create two test files with different content | ||
let test_file1_path = temp_dir.join("file.txt"); | ||
let test_content1 = b"This is the file content"; | ||
let mut file1 = fs::File::create(&test_file1_path).unwrap(); | ||
file1.write_all(test_content1).unwrap(); | ||
|
||
let test_file2_path = temp_dir.join("another_file.txt"); | ||
let test_content2 = b"This is another file content"; | ||
let mut file2 = fs::File::create(&test_file2_path).unwrap(); | ||
file2.write_all(test_content2).unwrap(); | ||
|
||
// Create aliases mapping | ||
let mut aliases = HashMap::new(); | ||
aliases.insert("alias1".to_string(), test_file1_path.clone()); | ||
aliases.insert("alias2".to_string(), test_file2_path.clone()); | ||
aliases.insert("deployment-id".to_string(), test_file1_path.clone()); | ||
|
||
// Create resolver with aliases | ||
let resolver = FileLinkResolver::new(Some(temp_dir.clone()), aliases); | ||
let logger = slog::Logger::root(slog::Discard, slog::o!()); | ||
|
||
// Test resolving by aliases | ||
let link1 = Link { | ||
link: "alias1".to_string(), | ||
}; | ||
let result1 = resolver.cat(&logger, &link1).await.unwrap(); | ||
assert_eq!(result1, test_content1); | ||
|
||
let link2 = Link { | ||
link: "alias2".to_string(), | ||
}; | ||
let result2 = resolver.cat(&logger, &link2).await.unwrap(); | ||
assert_eq!(result2, test_content2); | ||
|
||
// Test that the alias works in for_deployment as well | ||
let deployment_resolver = resolver.clone_for_manifest("deployment-id").unwrap(); | ||
|
||
let expected_dir = test_file1_path.parent().unwrap(); | ||
let deployment_base_dir = deployment_resolver.base_dir.clone().unwrap(); | ||
|
||
let canonical_expected_dir = expected_dir.canonicalize().unwrap(); | ||
let canonical_deployment_dir = deployment_base_dir.canonicalize().unwrap(); | ||
|
||
assert_eq!( | ||
canonical_deployment_dir, canonical_expected_dir, | ||
"Build directory paths don't match" | ||
); | ||
|
||
// Clean up | ||
let _ = fs::remove_file(test_file1_path); | ||
let _ = fs::remove_file(test_file2_path); | ||
let _ = fs::remove_dir(temp_dir); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be clearer if
aliases
was aHashMap<DeploymentHash, PathBuf>
which is really what it expresses: where in the filesystem one would find the files for a given deployment.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it a bit strange to do that, because aliases represents is a mapping from the String aliases that the user provided as cli arguments, yes it does get converted into
DeploymentHash
but with the recent change tofn for_manifest(&self, manifest_path: &str)
fromfn for_deployment(&self, deployment: DeploymentHash)
Also in the
cat
method we would need to convert theLink
again back toDeploymentHash
which is a bit wierd, since Link was created by callingto_ipfs_link
onDeploymentHash
. Incat
we use this to resolve aliases properly