diff --git a/packages/fuel-indexer-lib/src/manifest.rs b/packages/fuel-indexer-lib/src/manifest.rs index 20e8dcbd4..293adb410 100644 --- a/packages/fuel-indexer-lib/src/manifest.rs +++ b/packages/fuel-indexer-lib/src/manifest.rs @@ -27,6 +27,7 @@ pub enum ManifestError { /// /// When using a `Wasm` module, the WASM binary at the given path /// is read and those bytes are registered into a `WasmIndexerExecutor`. +/// /// `Native` modules on the other hand do not require a path, because /// native indexers compile to binaries that can be executed without having /// to read the bytes of some compiled module. @@ -63,12 +64,175 @@ impl AsRef for Module { } } +/// Write some bytes content to a general path. +fn write_content(path: &PathBuf, content: Vec) -> ManifestResult<()> { + let mut file = File::create(path).map_err(|err| { + ManifestError::FileError(path.to_str().unwrap_or_default().to_string(), err) + })?; + file.write_all(&content).map_err(|err| { + ManifestError::FileError(path.to_str().unwrap_or_default().to_string(), err) + })?; + Ok(()) +} + +/// Represents a line of raw manifest content. +#[derive(Debug)] +pub struct RawManifestLine(pub usize, pub String); + +/// Represents the raw manifest file. +/// +/// This includes comments and other tokens that `serde_yaml` will not serialize/deserialize. +/// +/// While the primary `Manifest` is used within the project for ease-of-use (due to the ability +/// to `#[derive(Serialize, Deserialize)]` using `serde_yaml`), this `RawManifest` is used as a +/// thin layer for caching comments on file read, and writing comments on file write. +/// +/// This `RawManifest` serves no other purpose outside of `plugins::forc_index::ops::forc_index_build`. +pub struct RawManifest { + /// Comments in the manifest file. + comments: Vec, + + /// YAML content of the manifest file (not including comments). + yaml: Vec, +} + +impl RawManifest { + /// Return the YAML content of this manifest. + pub fn yaml(&self) -> &Vec { + &self.yaml + } + + /// Derive an indexer manifest via the YAML file at the specified path. + pub fn from_file(path: impl AsRef) -> ManifestResult { + let mut file = File::open(&path).map_err(|e| { + ManifestError::FileError(path.as_ref().display().to_string(), e) + })?; + let mut content = String::new(); + file.read_to_string(&mut content).map_err(|e| { + ManifestError::FileError(path.as_ref().display().to_string(), e) + })?; + + let comments = content + .lines() + .enumerate() + .filter_map(|(i, line)| { + if line.starts_with('#') { + Some(RawManifestLine(i, line.to_string())) + } else { + None + } + }) + .collect::>(); + + let yaml = content + .lines() + .enumerate() + .filter_map(|(i, line)| { + if !line.starts_with('#') { + Some(RawManifestLine(i, line.to_string())) + } else { + None + } + }) + .collect::>(); + + Ok(Self { comments, yaml }) + } + + /// Update the YAML content of this manifest. + pub fn update_yaml(&mut self, manifest: &Manifest) -> ManifestResult<()> { + let serde_yaml_content = serde_yaml::to_string(&manifest)? + .lines() + .filter_map(|line| { + // NOTE: `serde_yaml` adds a new context when it serializes the manifest. + if line.is_empty() || line.starts_with("---") { + None + } else { + Some(line.to_string()) + } + }) + .collect::>(); + + let raw_yaml_content = self + .yaml + .iter() + .filter(|line| line.1.as_str().is_empty()) + .collect::>(); + + // `forc_index::ops::forc_index_build` will never add or remove any lines from the manifest, so + // we can assume the amount of raw lines should always equal the amount of lines serialized by + // `serde_yaml`. + assert_eq!(raw_yaml_content.len(), serde_yaml_content.len()); + + let new_yaml = raw_yaml_content + .iter() + .map(|line| { + let updated_line = serde_yaml_content.iter().find(|value| { + let value = value.as_str().split(':').next().unwrap().trim(); + let item = line.1.as_str().split(':').next().unwrap().trim(); + item == value + }); + + match updated_line { + Some(updated_line) => { + RawManifestLine(line.0, format!("{}\n", updated_line)) + } + None => RawManifestLine(line.0, format!("{}\n", line.1)), + } + }) + .collect::>(); + + self.yaml = new_yaml; + Ok(()) + } + + /// Write this manifest to a given path. + pub fn write(&self, path: &PathBuf) -> ManifestResult<()> { + let mut content = self + .comments + .iter() + .chain(self.yaml.iter()) + .collect::>(); + content.sort_by(|a, b| a.0.cmp(&b.0)); + + let content = content + .iter() + .map(|line| line.1.to_string()) + .collect::>() + .join("\n"); + write_content(path, content.into()) + } +} + +impl TryFrom<&RawManifest> for Manifest { + type Error = ManifestError; + /// Derive a `Manifest` from a `RawManifest`. + fn try_from(raw: &RawManifest) -> ManifestResult { + let yaml = raw + .yaml() + .iter() + .filter_map(|line| { + let line = line.1.as_str(); + if line.is_empty() { + None + } else { + Some(line) + } + }) + .collect::>() + .join("\n"); + let manifest: Manifest = serde_yaml::from_str(&yaml)?; + Ok(manifest) + } +} + /// Represents the indexer manifest file. /// /// This manifest file is a simple YAML file that is read and passed -/// to the excecutor to which the indexer is registered. This manifest -/// specifies various properties of how the indexer should be run in -/// the indexer executor (e.g., Where should the indexing start?). +/// to the excecutor to which the indexer is registered. +/// +/// The manifest also specifies various properties of how the indexer should be run in +/// the indexer executor. #[derive(Debug, Deserialize, Serialize, Clone)] pub struct Manifest { /// Namespace of indexer. @@ -91,9 +255,6 @@ pub struct Manifest { /// Executor module. module: Module, - /// Whether or not to record metrics for this indexer. - metrics: Option, - /// Set of contract IDs this indexer should subscribe to. #[serde( serialize_with = "ContractIds::serialize", @@ -172,14 +333,7 @@ impl Manifest { /// Write this manifest to a given path. pub fn write(&self, path: &PathBuf) -> ManifestResult<()> { - let mut file = File::create(path).map_err(|err| { - ManifestError::FileError(path.to_str().unwrap_or_default().to_string(), err) - })?; - let content: Vec = Self::into(self.clone()); - file.write_all(&content).map_err(|err| { - ManifestError::FileError(path.to_str().unwrap_or_default().to_string(), err) - })?; - Ok(()) + write_content(path, self.into()) } /// Set the start block for this indexer. @@ -207,50 +361,62 @@ impl Manifest { self.abi = Some(abi); } + /// Get the namespace for this manifest. pub fn namespace(&self) -> &str { &self.namespace } + /// Set the namespace for this manifest. pub fn set_namespace(&mut self, namespace: String) { self.namespace = namespace; } + /// Set the identifier for this manifest. pub fn set_identifier(&mut self, identifier: String) { self.identifier = identifier; } + /// Get the identifier for this manifest. pub fn identifier(&self) -> &str { &self.identifier } + /// Get the GraphQL schema for this manifest. pub fn graphql_schema(&self) -> &str { &self.graphql_schema } + /// Get the start block for this manifest. pub fn start_block(&self) -> Option { self.start_block } + /// Get the contract IDs this manifest subscribes to. pub fn contract_id(&self) -> &ContractIds { &self.contract_id } + /// Get the contract ABI for this manifest. pub fn abi(&self) -> Option<&str> { self.abi.as_deref() } + /// Get the fuel client for this manifest. pub fn fuel_client(&self) -> Option<&str> { self.fuel_client.as_deref() } + /// Get the module for this manifest. pub fn module(&self) -> &Module { &self.module } + /// Get the end block for this manifest. pub fn end_block(&self) -> Option { self.end_block } + /// Get the resumable flag for this manifest. pub fn resumable(&self) -> Option { self.resumable } @@ -271,6 +437,12 @@ impl From for Vec { } } +impl From<&Manifest> for Vec { + fn from(manifest: &Manifest) -> Self { + serde_yaml::to_vec(manifest).unwrap() + } +} + impl TryFrom<&Vec> for Manifest { type Error = ManifestError; diff --git a/plugins/forc-index/src/defaults.rs b/plugins/forc-index/src/defaults.rs index 8cc2ad4e3..c150b4fbf 100644 --- a/plugins/forc-index/src/defaults.rs +++ b/plugins/forc-index/src/defaults.rs @@ -80,9 +80,13 @@ pub fn default_indexer_manifest( let module = if is_native { r#" + + # Native execution should not include any paths. native: ~"# } else { r#" + + # Path to web assembly module. wasm: ~"# }; diff --git a/plugins/forc-index/src/ops/forc_index_build.rs b/plugins/forc-index/src/ops/forc_index_build.rs index a83800f63..b7e7dae2c 100644 --- a/plugins/forc-index/src/ops/forc_index_build.rs +++ b/plugins/forc-index/src/ops/forc_index_build.rs @@ -1,6 +1,6 @@ use crate::{cli::BuildCommand, defaults, utils::project_dir_info}; use fuel_indexer_lib::{ - manifest::{Manifest, Module}, + manifest::{Manifest, Module, RawManifest}, utils::Config, }; use indicatif::{ProgressBar, ProgressStyle}; @@ -55,7 +55,8 @@ pub fn init(command: BuildCommand) -> anyhow::Result<()> { let config: Config = toml::from_str(&content)?; let indexer_manifest_path = root_dir.join(manifest); - let mut manifest = Manifest::from_file(&indexer_manifest_path)?; + let mut raw_manifest = RawManifest::from_file(&indexer_manifest_path)?; + let mut manifest = Manifest::try_from(&raw_manifest)?; let manifest_schema_file = { let workspace_root: std::path::PathBuf = @@ -203,8 +204,8 @@ pub fn init(command: BuildCommand) -> anyhow::Result<()> { anyhow::bail!("❌ Failed to execute wasm-snip: (Code: {code:?})",) } - // FIXME: This should include whatever comments were in the original doc - manifest.write(&indexer_manifest_path)?; + raw_manifest.update_yaml(&manifest)?; + raw_manifest.write(&indexer_manifest_path)?; } Ok(())