mercury_cli/
parser.rs

1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use std::{
4    fs::File,
5    io::{BufRead, Read},
6    path::Path,
7    process::Command,
8};
9
10use crate::{
11    error::ParserError,
12    specification::{Dashboard, Index},
13    MercuryClient,
14};
15
16impl Config {
17    fn tables(&self) -> Vec<Table> {
18        self.tables.clone().unwrap_or(vec![])
19    }
20}
21
22#[derive(Deserialize, Serialize, Clone)]
23pub struct Config {
24    pub name: String,
25
26    /// Optionally set the project name.
27    pub project: Option<String>,
28
29    /// Tables that the poject is writing or reading.
30    pub tables: Option<Vec<Table>>,
31
32    /// Declared public indexes to register.
33    pub indexes: Option<Vec<Index>>,
34
35    /// Declared dashboard (if any) to register.
36    pub dashboard: Option<Dashboard>,
37}
38
39#[derive(Deserialize, Serialize, Clone)]
40pub struct Table {
41    pub name: String,
42    pub columns: Vec<Column>,
43    pub force: Option<bool>,
44}
45
46#[derive(Deserialize, Serialize, Debug, Clone)]
47pub struct Column {
48    pub name: String,
49    pub col_type: String,
50    pub primary: Option<bool>,
51    pub index: Option<bool>,
52}
53
54pub struct ZephyrProjectParser {
55    pub(crate) config: Config,
56    pub(crate) client: MercuryClient,
57}
58
59impl ZephyrProjectParser {
60    pub fn from_path<P: AsRef<Path>>(client: MercuryClient, path: P) -> Result<Self> {
61        let project_definition = {
62            let mut content = String::new();
63            File::open(path)?.read_to_string(&mut content)?;
64
65            content
66        };
67
68        let parser = Self {
69            client,
70            config: toml::from_str(&project_definition)?,
71        };
72
73        Ok(parser)
74    }
75
76    pub fn build_wasm(&self) -> Result<()> {
77        let mut child = Command::new("cargo")
78            .args(&["build", "--release", "--target=wasm32-unknown-unknown"])
79            .stdout(std::process::Stdio::piped())
80            .stderr(std::process::Stdio::piped())
81            .spawn()?;
82
83        let stdout = child.stdout.take().unwrap();
84        let stderr = child.stderr.take().unwrap();
85
86        let stdout_thread = std::thread::spawn(move || {
87            let reader = std::io::BufReader::new(stdout);
88            for line in reader.lines() {
89                if let Ok(line) = line {
90                    println!("{}", line);
91                }
92            }
93        });
94
95        let stderr_thread = std::thread::spawn(move || {
96            let reader = std::io::BufReader::new(stderr);
97            for line in reader.lines() {
98                if let Ok(line) = line {
99                    eprintln!("{}", line);
100                }
101            }
102        });
103
104        let status = child.wait()?;
105        stdout_thread.join().unwrap();
106        stderr_thread.join().unwrap();
107
108        if !status.success() {
109            return Err(ParserError::WasmBuildError("Build failed".to_string()).into());
110        }
111
112        Ok(())
113    }
114
115    pub async fn deploy_tables(&self, force: bool) -> Result<()> {
116        for table in self.config.tables() {
117            if let Err(_) = self.client.new_table(table, force).await {
118                return Err(ParserError::TableCreationError.into());
119            };
120        }
121
122        Ok(())
123    }
124
125    pub async fn deploy_wasm(&self, target: Option<String>) -> Result<()> {
126        let project_name = &self.config.name;
127        let path = if let Some(target_dir) = target {
128            format!("{}/{}.wasm", target_dir, project_name.replace('-', "_"))
129        } else {
130            format!(
131                "./target/wasm32-unknown-unknown/release/{}.wasm",
132                project_name.replace('-', "_")
133            )
134        };
135
136        let project_name = if let Some(pname) = self.config.project.clone() {
137            pname
138        } else {
139            self.config.name.clone()
140        };
141
142        if let Err(_) = self.client.deploy(path, Some(project_name), None).await {
143            return Err(ParserError::WasmDeploymentError.into());
144        };
145
146        Ok(())
147    }
148}
149
150#[cfg(test)]
151mod test {
152    use super::{Column, Config, Table};
153
154    #[test]
155    pub fn sample_config() {
156        let config = Config {
157            name: "zephyr-soroban-op-ratio".into(),
158            project: None,
159            indexes: None,
160            dashboard: None,
161            tables: Some(vec![Table {
162                name: "opratio".into(),
163                columns: vec![
164                    Column {
165                        name: "soroban".into(),
166                        col_type: "BYTEA".into(), // only supported type as of now
167                    },
168                    Column {
169                        name: "ratio".into(),
170                        col_type: "BYTEA".into(), // only supported type as of now
171                    },
172                ],
173            }]),
174        };
175
176        println!("{}", toml::to_string(&config).unwrap());
177    }
178}