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 pub project: Option<String>,
28
29 pub tables: Option<Vec<Table>>,
31
32 pub indexes: Option<Vec<Index>>,
34
35 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(), },
168 Column {
169 name: "ratio".into(),
170 col_type: "BYTEA".into(), },
172 ],
173 }]),
174 };
175
176 println!("{}", toml::to_string(&config).unwrap());
177 }
178}