1#![doc = include_str!("../README.md")]
2#![warn(clippy::all, clippy::pedantic)]
3#![allow(clippy::missing_errors_doc)]
4
5pub mod content;
10pub mod project;
11
12use anyhow::bail;
13use ordinary_config::{
14 ActionConfig, ActionFfi, ActionFfiSerialization, ActionFfiVersion, IntegrationConfig,
15 IntegrationProtocol, ModelConfig, OrdinaryConfig, UuidVersion,
16};
17use std::{io::Write, path::Path};
18use tracing::instrument;
19
20#[instrument(err)]
21#[allow(
22 clippy::too_many_arguments,
23 clippy::used_underscore_binding,
24 clippy::too_many_lines
25)]
26pub fn add_action(
27 path: &str,
28 name: &str,
29 lang: &str,
30 _access: (),
31 _accepts: (),
32 _returns: (),
33 transactional: &Option<bool>,
34 _protected: (),
35) -> anyhow::Result<()> {
36 let proj_path = Path::new(path);
37 let mut app_config = OrdinaryConfig::get(path, false)?;
38
39 let mut actions = app_config.actions.unwrap_or_default();
40
41 if actions.len() <= 225 {
42 let next_idx = actions.len();
43
44 let language = match lang {
45 "Rust" | "rust" | "rs" => ordinary_config::ActionLang::Rust,
46 "JavaScript" | "javascript" | "JS" | "js" => ordinary_config::ActionLang::JavaScript,
47 _ => bail!("language {lang} not yet supported"),
48 };
49
50 let action_dir_path = proj_path.join("actions").join(name);
51 fs_err::create_dir_all(&action_dir_path)?;
52
53 match language {
54 ordinary_config::ActionLang::Rust => {
55 let gitignore = "/target";
56
57 let cargo_toml = format!(
58 r#"[workspace]
59
60[package]
61name = "action"
62version = "0.1.0"
63edition = "2024"
64
65[dependencies]
66ordinary = {{ path = "../../.ordinary/gen/actions/{name}" }}
67
68[profile.release]
69strip = "symbols"
70lto = "fat"
71opt-level = "z"
72codegen-units = 1
73panic = "abort"
74"#
75 );
76
77 let main_rs = r#"use std::error::Error;
78use ordinary::{recv_in, send_out};
79
80fn main() -> Result<(), Box<dyn Error>> {
81 let _input = recv_in()?;
82
83 ordinary::trace(2, "Hello, Ordinary!")?;
84
85 send_out(())
86}
87"#;
88
89 let mut gitignore_file = fs_err::File::create(action_dir_path.join(".gitignore"))?;
90 gitignore_file.write_all(gitignore.as_bytes())?;
91
92 let mut cargo_toml_file = fs_err::File::create(action_dir_path.join("Cargo.toml"))?;
93 cargo_toml_file.write_all(cargo_toml.as_bytes())?;
94
95 fs_err::create_dir_all(action_dir_path.join("src"))?;
96 let mut main_rs_file =
97 fs_err::File::create(action_dir_path.join("src").join("main.rs"))?;
98 main_rs_file.write_all(main_rs.as_bytes())?;
99 }
100 ordinary_config::ActionLang::JavaScript => {
101 let gitignore = r"/target
102node_modules";
103
104 let package_json = r#"{
105 "name": "action",
106 "version": "0.1.0",
107 "main": "index.js",
108 "files": [
109 "index.js",
110 "index.d.ts"
111 ],
112 "types": "client.d.ts",
113 "scripts": {
114 "build": "esbuild main.js --bundle --minify --outfile=index.js --target=es2020 --format=iife --global-name=action"
115 },
116 "devDependencies": {
117 "esbuild": "0.25.10"
118 }
119}
120 "#;
121
122 let main_js = r#"export function main(input) {
123 console.log("Hello, Ordinary!");
124}"#;
125
126 let mut gitignore_file = fs_err::File::create(action_dir_path.join(".gitignore"))?;
127 gitignore_file.write_all(gitignore.as_bytes())?;
128
129 let mut package_json_file =
130 fs_err::File::create(action_dir_path.join("package.json"))?;
131 package_json_file.write_all(package_json.as_bytes())?;
132
133 let mut main_js_file = fs_err::File::create(action_dir_path.join("main.js"))?;
134 main_js_file.write_all(main_js.as_bytes())?;
135 }
136 }
137
138 actions.push(ActionConfig {
139 ffi: ActionFfi {
140 version: ActionFfiVersion::V1,
141 serialization: ActionFfiSerialization::FlexBufferVector,
142 },
143 idx: u8::try_from(next_idx)?,
144 dir_path: Some(format!("./actions/{name}")),
145 name: name.to_string(),
146 lang: language,
147 access: vec![],
148 accepts: ordinary_types::Kind::Void,
149 returns: ordinary_types::Kind::Void,
150 triggered_by: vec![],
151 transactional: *transactional,
152 protected: None,
153 timeout: None,
154 cors: None,
155 privileged: None,
156 variables: None,
157 middlewares: None,
158 });
159
160 app_config.actions = Some(actions);
161
162 let ordinary_json = serde_json::to_string_pretty(&app_config)?;
163
164 let mut file = fs_err::File::create(proj_path.join("ordinary.json"))?;
165 file.write_all(ordinary_json.as_bytes())?;
166 } else {
167 tracing::error!("cannot support more than 255 actions for a single project.");
168 }
169
170 Ok(())
171}
172
173#[instrument(err)]
174pub fn add_integration(
175 path: &str,
176 name: &str,
177 endpoint: &str,
178 protocol: &str,
179) -> anyhow::Result<()> {
180 let proj_path = Path::new(path);
181 let mut app_config = OrdinaryConfig::get(path, false)?;
182
183 let mut integrations = app_config.integrations.unwrap_or_default();
184
185 if integrations.len() <= 255 {
186 let next_idx = integrations.len();
187
188 integrations.push(IntegrationConfig {
189 idx: u8::try_from(next_idx)?,
190 name: name.to_string(),
191 protocol: match protocol {
192 "JSON" => IntegrationProtocol::Http {
193 method: "GET".to_string(),
194 headers: vec![],
195 send_encoding: ordinary_config::IntegrationProtocolHttpEncoding::Text,
196 recv_encoding: ordinary_config::IntegrationProtocolHttpEncoding::Text,
197 },
198 _ => bail!("invalid protocol"),
199 },
200 endpoint: endpoint.to_string(),
201 send: ordinary_types::Kind::Json,
202 recv: ordinary_types::Kind::Json,
203 secrets: None,
204 timeout: None,
205 });
206
207 app_config.integrations = Some(integrations);
208
209 let ordinary_json = serde_json::to_string_pretty(&app_config)?;
210
211 let mut file = fs_err::File::create(proj_path.join("ordinary.json"))?;
212 file.write_all(ordinary_json.as_bytes())?;
213 } else {
214 tracing::error!("cannot support more than 255 integrations for a single project.");
215 }
216
217 Ok(())
218}
219
220#[instrument(err)]
221pub fn add_model(path: &str, name: &str, uuid: Option<&str>) -> anyhow::Result<()> {
222 let proj_path = Path::new(path);
223 let mut app_config = OrdinaryConfig::get(path, false)?;
224
225 let mut models = app_config.models.unwrap_or_default();
226
227 if models.len() <= 255 {
228 let next_idx = models.len();
229
230 models.push(ModelConfig {
231 idx: u8::try_from(next_idx)?,
232 name: name.to_string(),
233 fields: vec![],
234 uuid: match uuid {
235 Some(uuid) => match uuid {
236 "v4" => Some(UuidVersion::V4),
237 "v7" => Some(UuidVersion::V7),
238 _ => bail!("invalid UUID version"),
239 },
240 None => None,
241 },
242 });
243
244 app_config.models = Some(models);
245
246 let ordinary_json = serde_json::to_string_pretty(&app_config)?;
247
248 let mut file = fs_err::File::create(proj_path.join("ordinary.json"))?;
249 file.write_all(ordinary_json.as_bytes())?;
250 } else {
251 tracing::error!("cannot support more than 255 models for a single project.");
252 }
253
254 Ok(())
255}