Skip to content
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

chore: forc-index-build preserve yaml comments #1415

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ graphql_schema: examples/fuel-explorer/fuel-explorer/schema/fuel_explorer.schem
# of the indexer.
# Important: At this time, wasm is the preferred method of execution.
module:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add an empty line here? Is this something the comment-preserving code is forcing us to do?

Copy link
Contributor Author

@ra0x3 ra0x3 Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lostman

  • Well the alternative to simply joining each line in the manifest on a newline, is - finding the particular lines that (we think) shouldn't have new lines above them (e.g., wasm: ~) and removing the newline in-between this line and its precursor line
    • Nothing forcing us to not do this, I just thought it was a little much for something small/subjective

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I think that's fine. What I expected is that nested items would not be separated by empty lines:

toplevel:
  nested: 123

another-top-level:
  another-nested: 123

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lostman Let me push up a change

wasm: target/wasm32-unknown-unknown/release/fuel_explorer.wasm

# The resumable field contains a boolean that specifies whether or not the indexer should, synchronise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ graphql_schema: examples/greetings/greetings-indexer/schema/greetings_indexer.sc
# of the indexer.
# Important: At this time, wasm is the preferred method of execution.
module:

wasm: target/wasm32-unknown-unknown/release/greetings_indexer.wasm

# The resumable field contains a boolean that specifies whether or not the indexer should, synchronise
# with the latest block if it has fallen out of sync.
resumable: true
resumable: true
200 changes: 186 additions & 14 deletions packages/fuel-indexer-lib/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -63,12 +64,175 @@ impl AsRef<Path> for Module {
}
}

/// Write some bytes content to a general path.
fn write_content(path: &PathBuf, content: Vec<u8>) -> 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<RawManifestLine>,

/// YAML content of the manifest file (not including comments).
yaml: Vec<RawManifestLine>,
}

impl RawManifest {
/// Return the YAML content of this manifest.
pub fn yaml(&self) -> &Vec<RawManifestLine> {
&self.yaml
}

/// Derive an indexer manifest via the YAML file at the specified path.
pub fn from_file(path: impl AsRef<Path>) -> ManifestResult<Self> {
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::<Vec<RawManifestLine>>();

let yaml = content
.lines()
.enumerate()
.filter_map(|(i, line)| {
if !line.starts_with('#') {
Some(RawManifestLine(i, line.to_string()))
} else {
None
}
})
.collect::<Vec<RawManifestLine>>();

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::<Vec<_>>();

let raw_yaml_content = self
.yaml
.iter()
.filter(|line| !line.1.as_str().is_empty())
.collect::<Vec<_>>();

// `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::<Vec<_>>();

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::<Vec<_>>();
content.sort_by(|a, b| a.0.cmp(&b.0));

let content = content
.iter()
.map(|line| line.1.to_string())
.collect::<Vec<_>>()
.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<Self> {
let yaml = raw
.yaml()
.iter()
.filter_map(|line| {
let line = line.1.as_str();
if line.is_empty() {
None
} else {
Some(line)
}
})
.collect::<Vec<_>>()
.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.
Expand All @@ -91,9 +255,6 @@ pub struct Manifest {
/// Executor module.
module: Module,

/// Whether or not to record metrics for this indexer.
metrics: Option<bool>,

/// Set of contract IDs this indexer should subscribe to.
#[serde(
serialize_with = "ContractIds::serialize",
Expand Down Expand Up @@ -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<u8> = 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.
Expand Down Expand Up @@ -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<u32> {
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<u32> {
self.end_block
}

/// Get the resumable flag for this manifest.
pub fn resumable(&self) -> Option<bool> {
self.resumable
}
Expand All @@ -271,6 +437,12 @@ impl From<Manifest> for Vec<u8> {
}
}

impl From<&Manifest> for Vec<u8> {
fn from(manifest: &Manifest) -> Self {
serde_yaml::to_vec(manifest).unwrap()
}
}

impl TryFrom<&Vec<u8>> for Manifest {
type Error = ManifestError;

Expand Down
4 changes: 4 additions & 0 deletions plugins/forc-index/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: ~"#
};

Expand Down
9 changes: 5 additions & 4 deletions plugins/forc-index/src/ops/forc_index_build.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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(())
Expand Down
Loading