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)]
29pub enum LoadError {
30 #[error("failed to read {}: {source}", path.display())]
31 Io {
32 path: std::path::PathBuf,
33 source: std::io::Error,
34 },
35 #[error("failed to parse {}: {source}", path.display())]
36 Parse {
37 path: std::path::PathBuf,
38 source: ParseError,
39 },
40}
41
42pub fn load_program(path: &Path) -> Result<Program, LoadError> {
43 let source = std::fs::read_to_string(path).map_err(|err| LoadError::Io {
44 path: path.to_path_buf(),
45 source: err,
46 })?;
47 parse(&source).map_err(|err| LoadError::Parse {
48 path: path.to_path_buf(),
49 source: err,
50 })
51}
52
53pub fn compile_program(program: &Program) -> Result<BytecodeProgram, CompileError> {
54 compile(program)
55}
56
57pub fn compile_to_bytes(program: &Program) -> Result<Vec<u8>, CompileError> {
58 Ok(encode_bytecode(&compile_program(program)?))
59}
60
61pub async fn run(
62 program: &Program,
63 config: ruso_runtime::ExecutorConfig,
64) -> Result<ruso_runtime::ExecutionResult, ruso_runtime::RuntimeError> {
65 let bytecode =
66 compile_program(program).map_err(|e| ruso_runtime::RuntimeError::Other(e.to_string()))?;
67 ruso_runtime::Executor::from_bytecode(config, bytecode)?
68 .run()
69 .await
70}
71
72pub async fn run_bytecode(
73 bytecode: &BytecodeProgram,
74 config: ruso_runtime::ExecutorConfig,
75) -> Result<ruso_runtime::ExecutionResult, ruso_runtime::RuntimeError> {
76 ruso_runtime::Executor::from_bytecode(config, bytecode.clone())?
77 .run()
78 .await
79}
80
81pub async fn run_program(
87 bytecode: std::sync::Arc<BytecodeProgram>,
88 config: ruso_runtime::ExecutorConfig,
89) -> Result<ruso_runtime::ExecutionResult, ruso_runtime::RuntimeError> {
90 ruso_runtime::Executor::from_program(config, bytecode)?
91 .run()
92 .await
93}
94
95pub async fn run_bytes(
96 bytes: &[u8],
97 config: ruso_runtime::ExecutorConfig,
98) -> Result<ruso_runtime::ExecutionResult, ruso_runtime::RuntimeError> {
99 ruso_runtime::Executor::from_bytes(config, bytes)?
100 .run()
101 .await
102}
103
104#[cfg(test)]
105mod tests {
106 use std::path::PathBuf;
107
108 use ruso_runtime::{bytes_to_hex, decode_bytecode, hex_to_bytes};
109
110 use super::*;
111 use crate::script::ast::Stmt;
112
113 #[test]
114 fn load_program_valid_example() {
115 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/http_status_ok.rsl");
116 let program = load_program(&path).expect("example script should parse");
117 assert!(!program.statements.is_empty());
118 }
119
120 #[test]
121 fn bytecode_roundtrip_http_example() {
122 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/http_status_ok.rsl");
123 let program = load_program(&path).unwrap();
124 let original = compile_program(&program).unwrap();
125 let bytes = encode_bytecode(&original);
126 let restored = decode_bytecode(&bytes).unwrap();
127 assert_eq!(original.code, restored.code);
128 assert_eq!(original.strings, restored.strings);
129 assert_eq!(original.matchers, restored.matchers);
130 }
131
132 #[test]
133 fn hex_bytes_roundtrip() {
134 let program = Program {
135 statements: vec![Stmt::Name("Hex test".into()), Stmt::Severity(Severity::Low)],
136 };
137 let bytes = compile_to_bytes(&program).unwrap();
138 let hex = bytes_to_hex(&bytes);
139 let restored = hex_to_bytes(&hex).expect("hex decode");
140 let decoded = decode_bytecode(&restored).expect("bytecode decode");
141 assert_eq!(decoded.spec.metadata.name.as_deref(), Some("Hex test"));
142 }
143}