1#![allow(clippy::result_large_err)]
14
15mod compile;
16pub mod script;
17mod spec_build;
18
19pub use compile::{CompileError, compile};
20pub use ruso_runtime::{
21 BytecodeProgram, EvidenceKind, ExtractSource, QualifiedMatch, Severity, encode_bytecode,
22};
23pub use script::ast::{self, Program, Stmt};
24pub use script::{ParseError, parse};
25
26use std::path::Path;
27
28#[derive(Debug, thiserror::Error)]
31pub enum LoadError {
32 #[error("failed to read {}: {source}", path.display())]
34 Io {
35 path: std::path::PathBuf,
37 source: std::io::Error,
39 },
40 #[error("failed to parse {}: {source}", path.display())]
42 Parse {
43 path: std::path::PathBuf,
45 source: ParseError,
47 },
48}
49
50pub fn load_program(path: &Path) -> Result<Program, LoadError> {
52 let source = std::fs::read_to_string(path).map_err(|err| LoadError::Io {
53 path: path.to_path_buf(),
54 source: err,
55 })?;
56 parse(&source).map_err(|err| LoadError::Parse {
57 path: path.to_path_buf(),
58 source: err,
59 })
60}
61
62pub fn compile_program(program: &Program) -> Result<BytecodeProgram, CompileError> {
64 compile(program)
65}
66
67pub fn compile_to_bytes(program: &Program) -> Result<Vec<u8>, CompileError> {
69 Ok(encode_bytecode(&compile_program(program)?))
70}
71
72pub async fn run(
74 program: &Program,
75 config: ruso_runtime::ExecutorConfig,
76) -> Result<ruso_runtime::ExecutionResult, ruso_runtime::RuntimeError> {
77 let bytecode =
78 compile_program(program).map_err(|e| ruso_runtime::RuntimeError::Other(e.to_string()))?;
79 ruso_runtime::Executor::from_bytecode(config, bytecode)?
80 .run()
81 .await
82}
83
84pub async fn run_bytecode(
86 bytecode: &BytecodeProgram,
87 config: ruso_runtime::ExecutorConfig,
88) -> Result<ruso_runtime::ExecutionResult, ruso_runtime::RuntimeError> {
89 ruso_runtime::Executor::from_bytecode(config, bytecode.clone())?
90 .run()
91 .await
92}
93
94pub async fn run_program(
100 bytecode: std::sync::Arc<BytecodeProgram>,
101 config: ruso_runtime::ExecutorConfig,
102) -> Result<ruso_runtime::ExecutionResult, ruso_runtime::RuntimeError> {
103 ruso_runtime::Executor::from_program(config, bytecode)?
104 .run()
105 .await
106}
107
108pub async fn run_bytes(
110 bytes: &[u8],
111 config: ruso_runtime::ExecutorConfig,
112) -> Result<ruso_runtime::ExecutionResult, ruso_runtime::RuntimeError> {
113 ruso_runtime::Executor::from_bytes(config, bytes)?
114 .run()
115 .await
116}
117
118#[cfg(test)]
119mod tests {
120 use std::path::PathBuf;
121
122 use ruso_runtime::{bytes_to_hex, decode_bytecode, hex_to_bytes};
123
124 use super::*;
125 use crate::script::ast::Stmt;
126
127 #[test]
128 fn load_program_valid_example() {
129 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/http_status_ok.rsl");
130 let program = load_program(&path).expect("example script should parse");
131 assert!(!program.statements.is_empty());
132 }
133
134 #[test]
135 fn bytecode_roundtrip_http_example() {
136 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/http_status_ok.rsl");
137 let program = load_program(&path).unwrap();
138 let original = compile_program(&program).unwrap();
139 let bytes = encode_bytecode(&original);
140 let restored = decode_bytecode(&bytes).unwrap();
141 assert_eq!(original.code, restored.code);
142 assert_eq!(original.strings, restored.strings);
143 assert_eq!(original.matchers, restored.matchers);
144 }
145
146 #[test]
147 fn hex_bytes_roundtrip() {
148 let program = Program {
149 statements: vec![Stmt::Name("Hex test".into()), Stmt::Severity(Severity::Low)],
150 };
151 let bytes = compile_to_bytes(&program).unwrap();
152 let hex = bytes_to_hex(&bytes);
153 let restored = hex_to_bytes(&hex).expect("hex decode");
154 let decoded = decode_bytecode(&restored).expect("bytecode decode");
155 assert_eq!(decoded.spec.metadata.name.as_deref(), Some("Hex test"));
156 }
157}