use thiserror::Error;
use crate::config::{Config, Language};
use crate::isolate::{IsolateBox, IsolateError};
pub use crate::runner::compile::{CompileResult, compile};
pub use crate::runner::execute::{execute, execute_interpreted};
pub use crate::runner::interactive::{
InteractiveEvent, InteractiveEventStream, InteractiveSession, InteractiveSessionHandle,
};
use crate::types::{ExecutionResult, ResourceLimits};
mod compile;
mod execute;
mod interactive;
#[derive(Debug)]
pub struct CompileAndRunRequest<'a> {
pub sandbox: &'a IsolateBox,
pub source: &'a [u8],
pub input: Option<&'a [u8]>,
pub language: &'a Language,
pub compile_limits: Option<&'a ResourceLimits>,
pub run_limits: Option<&'a ResourceLimits>,
}
#[derive(Debug, Error)]
pub enum CompileError {
#[error("compilation failed with exit code {exit_code}: {stderr}")]
Failed { exit_code: i32, stderr: String },
#[error("compilation timed out")]
Timeout,
#[error("language '{0}' does not support compilation")]
NotCompiled(String),
#[error("isolate error: {0}")]
Isolate(#[from] IsolateError),
}
#[derive(Debug, Error)]
pub enum ExecuteError {
#[error("execution not started: {0}")]
NotStarted(String),
#[error("isolate error: {0}")]
Isolate(#[from] IsolateError),
}
#[derive(Debug, Error)]
pub enum InteractiveError {
#[error("session not started")]
NotStarted,
#[error("session already terminated")]
Terminated,
#[error("failed to create FIFO: {0}")]
FifoCreation(#[source] std::io::Error),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("isolate error: {0}")]
Isolate(#[from] IsolateError),
#[error("wait timed out")]
Timeout,
}
#[derive(Debug, Error)]
pub enum CompileAndRunError {
#[error("compilation error: {0}")]
Compile(#[from] CompileError),
#[error("execution error: {0}")]
Execute(#[from] ExecuteError),
}
#[derive(Debug, Clone)]
pub struct Runner {
config: Config,
}
impl Runner {
pub fn new(config: Config) -> Self {
Self { config }
}
pub fn with_defaults() -> Self {
Self {
config: Config::default(),
}
}
pub fn config(&self) -> &Config {
&self.config
}
pub async fn compile(
&self,
sandbox: &IsolateBox,
source: &[u8],
language: &Language,
limits: Option<&ResourceLimits>,
) -> Result<CompileResult, CompileError> {
compile::compile(sandbox, &self.config, language, source, limits).await
}
pub async fn run(
&self,
sandbox: &IsolateBox,
input: Option<&[u8]>,
language: &Language,
limits: Option<&ResourceLimits>,
) -> Result<ExecutionResult, ExecuteError> {
execute::execute(sandbox, &self.config, language, input, limits).await
}
pub async fn run_interpreted(
&self,
sandbox: &IsolateBox,
source: &[u8],
input: Option<&[u8]>,
language: &Language,
limits: Option<&ResourceLimits>,
) -> Result<ExecutionResult, ExecuteError> {
execute::execute_interpreted(sandbox, &self.config, language, source, input, limits).await
}
pub async fn run_interactive(
&self,
sandbox: &IsolateBox,
language: &Language,
limits: Option<&ResourceLimits>,
) -> Result<InteractiveSession, InteractiveError> {
InteractiveSession::start(sandbox, &self.config, language, limits).await
}
pub async fn compile_and_run(
&self,
request: CompileAndRunRequest<'_>,
) -> Result<(CompileResult, Option<ExecutionResult>), CompileAndRunError> {
let compile_result = self
.compile(
request.sandbox,
request.source,
request.language,
request.compile_limits,
)
.await?;
if compile_result.success {
let run_result = self
.run(
request.sandbox,
request.input,
request.language,
request.run_limits,
)
.await?;
Ok((compile_result, Some(run_result)))
} else {
Ok((compile_result, None))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_runner_creation() {
let runner = Runner::with_defaults();
assert!(runner.config().languages.contains_key("cpp17"));
assert!(runner.config().languages.contains_key("python3"));
}
}