Skip to content

Commit

Permalink
Return verdict; add support for file io
Browse files Browse the repository at this point in the history
  • Loading branch information
thecodingwizard committed Jul 17, 2024
1 parent f9c1518 commit afe76a4
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 21 deletions.
8 changes: 4 additions & 4 deletions src/compile_and_execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
use crate::{
compile::{compile, CompileRequest},
error::AppError,
execute::{execute, ExecuteRequest},
run_command::{CommandOptions, CommandOutput},
execute::{execute, ExecuteOptions, ExecuteRequest, ExecuteResponse},
run_command::CommandOutput,
};

/// Payload for POST /compile-and-execute
Expand All @@ -15,15 +15,15 @@ use crate::{
#[derive(Deserialize)]
pub struct CompileAndExecuteRequest {
pub compile: CompileRequest,
pub execute: CommandOptions,
pub execute: ExecuteOptions,
}

/// Response for POST /compile-and-execute
#[derive(Serialize)]
pub struct CompileAndExecuteResponse {
pub compile: CommandOutput,
/// None if the program failed to compile.
pub execute: Option<CommandOutput>,
pub execute: Option<ExecuteResponse>,
}

pub async fn compile_and_execute_handler(
Expand Down
84 changes: 74 additions & 10 deletions src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,81 @@ use std::{

use anyhow::{Result, anyhow};
use axum::Json;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use tempdir::TempDir;

use crate::{
error::AppError,
run_command::{run_command, CommandOptions, CommandOutput},
run_command::{run_command, CommandOptions},
types::{Executable, Language},
};
use base64::{prelude::BASE64_STANDARD, Engine};

#[derive(Deserialize)]
pub struct ExecuteRequest {
pub executable: Executable,
pub options: CommandOptions,
pub options: ExecuteOptions,
}

pub fn execute(payload: ExecuteRequest) -> Result<CommandOutput> {
#[derive(Deserialize)]
pub struct ExecuteOptions {
pub stdin: String,
pub timeout_ms: u32,

/// Alphanumeric string if you want file I/O to be supported, such as "cowdating".
///
/// Will create the files `file_io_name`.in and read `file_io_name`.out.
pub file_io_name: Option<String>,
}

#[derive(Serialize)]
pub enum Verdict {
#[serde(rename = "accepted")]
Accepted,
#[serde(rename = "wrong_answer")]
#[allow(dead_code)]
WrongAnswer,
#[serde(rename = "time_limit_exceeded")]
TimeLimitExceeded,
#[serde(rename = "runtime_error")]
RuntimeError,
}

#[derive(Serialize)]
pub struct ExecuteResponse {
pub stdout: String,
pub stderr: String,
pub wall_time: String, // time format is 0:00.00
pub memory_usage: String,

/// The underlying raw wait status. Note that this is different from an exit status.
pub exit_code: i32,
pub exit_signal: Option<String>,

pub verdict: Verdict,
}

pub fn execute(payload: ExecuteRequest) -> Result<ExecuteResponse> {
let tmp_dir = TempDir::new("execute")?;

match payload.executable {
if let Some(name) = payload.options.file_io_name {
if !name.chars().all(|c| c.is_ascii_alphanumeric()) {
return Err(anyhow!("Invalid file I/O name. It must be alphanumeric, like \"cowdating\"."))
}
let mut stdin_file = File::create(tmp_dir.path().join(name).with_extension("in"))?;
stdin_file.write_all(payload.options.stdin.as_ref())?;
}

let command_options = CommandOptions { stdin: payload.options.stdin, timeout_ms: payload.options.timeout_ms };

let command_output = match payload.executable {
Executable::Binary { value } => {
let mut executable_file = File::create(tmp_dir.path().join("program"))?;
executable_file.write_all(BASE64_STANDARD.decode(value)?.as_ref())?;
executable_file.set_permissions(Permissions::from_mode(0o755))?;
drop(executable_file);

run_command("./program", tmp_dir.path(), payload.options)
run_command("./program", tmp_dir.path(), command_options)
}
Executable::JavaClass { class_name, value } => {
let mut class_file = File::create(
Expand All @@ -47,7 +95,7 @@ pub fn execute(payload: ExecuteRequest) -> Result<CommandOutput> {
run_command(
format!("java {}", class_name).as_ref(),
tmp_dir.path(),
payload.options,
command_options,
)
}
Executable::Script {
Expand All @@ -63,13 +111,29 @@ pub fn execute(payload: ExecuteRequest) -> Result<CommandOutput> {
executable_file.set_permissions(Permissions::from_mode(0o755))?;
drop(executable_file);

run_command("python3.12 program.py", tmp_dir.path(), payload.options)
run_command("python3.12 program.py", tmp_dir.path(), command_options)
}
}
}?;

let verdict = match command_output.exit_code {
124 => Verdict::TimeLimitExceeded,
0 => Verdict::Accepted,
_ => Verdict::RuntimeError,
};

Ok(ExecuteResponse {
stdout: command_output.stdout,
stderr: command_output.stderr,
wall_time: command_output.wall_time,
memory_usage: command_output.memory_usage,
exit_code: command_output.exit_code,
exit_signal: command_output.exit_signal,
verdict,
})
}

pub async fn execute_handler(
Json(payload): Json<ExecuteRequest>,
) -> Result<Json<CommandOutput>, AppError> {
) -> Result<Json<ExecuteResponse>, AppError> {
Ok(Json(execute(payload)?))
}
7 changes: 0 additions & 7 deletions src/run_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,7 @@ pub struct CommandOutput {

/// The underlying raw wait status. Note that this is different from an exit status.
pub exit_code: i32,

pub exit_signal: Option<String>,
// /**
// * When executing, if `fileIOName` is given, this is
// * set to whatever is written in `[fileIOName].out`
// * or null if there's no such file.
// */
// pub file_output: Option<String>,
}

struct TimingOutput {
Expand Down

0 comments on commit afe76a4

Please sign in to comment.