/ vm / file / manifest.rs
manifest.rs
  1  // Copyright (c) 2025 ADnet Contributors
  2  // This file is part of the AlphaVM library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  use crate::{
 17      prelude::{Network, ProgramID},
 18      synthesizer::Program,
 19  };
 20  
 21  use alphavm_circuit::prelude::IndexMap;
 22  
 23  use anyhow::{Result, anyhow, ensure};
 24  use core::str::FromStr;
 25  use std::{
 26      fs::{self, File},
 27      io::Write,
 28      path::{Path, PathBuf},
 29  };
 30  
 31  const MANIFEST_FILE_NAME: &str = "program.json";
 32  
 33  pub struct Manifest<N: Network> {
 34      /// The file path.
 35      path: PathBuf,
 36      /// The program ID.
 37      program_id: ProgramID<N>,
 38      /// The program editions.
 39      editions: IndexMap<ProgramID<N>, u16>,
 40  }
 41  
 42  impl<N: Network> Manifest<N> {
 43      /// Creates a new manifest file with the given directory path and program ID.
 44      pub fn create(directory: &Path, id: &ProgramID<N>) -> Result<Self> {
 45          // Ensure the directory path exists.
 46          ensure!(directory.exists(), "The program directory does not exist: '{}'", directory.display());
 47          // Ensure the program name is valid.
 48          ensure!(!Program::is_reserved_keyword(id.name()), "Program name is invalid (reserved): {id}");
 49  
 50          // Construct the initial program manifest string.
 51          let manifest_string = format!(
 52              r#"{{
 53      "program": "{id}",
 54      "version": "0.0.0",
 55      "description": "",
 56      "license": "MIT",
 57      "editions": "{{}}"
 58  }}
 59  "#
 60          );
 61  
 62          // Construct the file path.
 63          let path = directory.join(MANIFEST_FILE_NAME);
 64          // Ensure the file path does not already exist.
 65          ensure!(!path.exists(), "Manifest file already exists: '{}'", path.display());
 66  
 67          // Write the file.
 68          File::create(&path)?.write_all(manifest_string.as_bytes())?;
 69  
 70          // Return the manifest file.
 71          Ok(Self { path, program_id: *id, editions: IndexMap::new() })
 72      }
 73  
 74      /// Opens the manifest file for reading.
 75      pub fn open(directory: &Path) -> Result<Self> {
 76          // Ensure the directory path exists.
 77          ensure!(directory.exists(), "The program directory does not exist: '{}'", directory.display());
 78  
 79          // Construct the file path.
 80          let path = directory.join(MANIFEST_FILE_NAME);
 81          // Ensure the file path exists.
 82          ensure!(path.exists(), "Manifest file is missing: '{}'", path.display());
 83  
 84          // Read the file to a string.
 85          let manifest_string = fs::read_to_string(&path)?;
 86          let json: serde_json::Value = serde_json::from_str(&manifest_string)?;
 87  
 88          // Retrieve the program ID.
 89          let id_string = json["program"].as_str().ok_or_else(|| anyhow!("Program ID not found."))?;
 90          let id = ProgramID::from_str(id_string)?;
 91          // Ensure the program name is valid.
 92          ensure!(!Program::is_reserved_keyword(id.name()), "Program name is invalid (reserved): {id}");
 93  
 94          // Initialize storage for the program editions.
 95          let mut editions = IndexMap::<ProgramID<N>, u16>::new();
 96  
 97          // Retrieve the editions in the manifest, if they exist.
 98          if let Some(editions_object) = json["editions"].as_object() {
 99              for (id_string, value) in editions_object {
100                  let id = ProgramID::from_str(id_string)?;
101                  let value = u16::try_from(value.as_u64().ok_or_else(|| anyhow!("The edition must be a number."))?)?;
102                  editions.insert(id, value);
103              }
104          }
105  
106          // Return the manifest file.
107          Ok(Self { path, program_id: id, editions })
108      }
109  
110      /// Returns `true` if the manifest file exists at the given path.
111      pub fn exists_at(directory: &Path) -> bool {
112          // Construct the file path.
113          let path = directory.join(MANIFEST_FILE_NAME);
114          // Return the result.
115          path.is_file() && path.exists()
116      }
117  
118      /// Returns the manifest file name.
119      pub const fn file_name() -> &'static str {
120          MANIFEST_FILE_NAME
121      }
122  
123      /// Returns the file path.
124      pub const fn path(&self) -> &PathBuf {
125          &self.path
126      }
127  
128      /// Returns the program ID.
129      pub const fn program_id(&self) -> &ProgramID<N> {
130          &self.program_id
131      }
132  
133      /// Returns the program editions.
134      pub const fn editions(&self) -> &IndexMap<ProgramID<N>, u16> {
135          &self.editions
136      }
137  }