Skip to content

Commit 0b82bb6

Browse files
Return verdict; add support for file io
1 parent f9c1518 commit 0b82bb6

File tree

3 files changed

+96
-23
lines changed

3 files changed

+96
-23
lines changed

src/compile_and_execute.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
44
use crate::{
55
compile::{compile, CompileRequest},
66
error::AppError,
7-
execute::{execute, ExecuteRequest},
8-
run_command::{CommandOptions, CommandOutput},
7+
execute::{execute, ExecuteOptions, ExecuteRequest, ExecuteResponse},
8+
run_command::CommandOutput,
99
};
1010

1111
/// Payload for POST /compile-and-execute
@@ -15,15 +15,15 @@ use crate::{
1515
#[derive(Deserialize)]
1616
pub struct CompileAndExecuteRequest {
1717
pub compile: CompileRequest,
18-
pub execute: CommandOptions,
18+
pub execute: ExecuteOptions,
1919
}
2020

2121
/// Response for POST /compile-and-execute
2222
#[derive(Serialize)]
2323
pub struct CompileAndExecuteResponse {
2424
pub compile: CommandOutput,
2525
/// None if the program failed to compile.
26-
pub execute: Option<CommandOutput>,
26+
pub execute: Option<ExecuteResponse>,
2727
}
2828

2929
pub async fn compile_and_execute_handler(

src/execute.rs

+92-12
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,90 @@
11
use std::{
2-
fs::{File, Permissions},
2+
fs::{self, File, Permissions},
33
io::Write,
4-
os::unix::fs::PermissionsExt,
4+
os::unix::fs::PermissionsExt, path::Path,
55
};
66

77
use anyhow::{Result, anyhow};
88
use axum::Json;
9-
use serde::Deserialize;
9+
use serde::{Deserialize, Serialize};
1010
use tempdir::TempDir;
1111

1212
use crate::{
1313
error::AppError,
14-
run_command::{run_command, CommandOptions, CommandOutput},
14+
run_command::{run_command, CommandOptions},
1515
types::{Executable, Language},
1616
};
1717
use base64::{prelude::BASE64_STANDARD, Engine};
1818

1919
#[derive(Deserialize)]
2020
pub struct ExecuteRequest {
2121
pub executable: Executable,
22-
pub options: CommandOptions,
22+
pub options: ExecuteOptions,
2323
}
2424

25-
pub fn execute(payload: ExecuteRequest) -> Result<CommandOutput> {
25+
#[derive(Deserialize)]
26+
pub struct ExecuteOptions {
27+
pub stdin: String,
28+
pub timeout_ms: u32,
29+
30+
/// Alphanumeric string if you want file I/O to be supported, such as "cowdating".
31+
///
32+
/// Will create the files `file_io_name`.in and read `file_io_name`.out.
33+
pub file_io_name: Option<String>,
34+
}
35+
36+
#[derive(Serialize)]
37+
pub enum Verdict {
38+
#[serde(rename = "accepted")]
39+
Accepted,
40+
#[serde(rename = "wrong_answer")]
41+
#[allow(dead_code)]
42+
WrongAnswer,
43+
#[serde(rename = "time_limit_exceeded")]
44+
TimeLimitExceeded,
45+
#[serde(rename = "runtime_error")]
46+
RuntimeError,
47+
}
48+
49+
#[derive(Serialize)]
50+
pub struct ExecuteResponse {
51+
pub stdout: String,
52+
53+
/// Only if `file_io_name`.out exists.
54+
pub file_output: Option<String>,
55+
56+
pub stderr: String,
57+
pub wall_time: String, // time format is 0:00.00
58+
pub memory_usage: String,
59+
60+
/// The underlying raw wait status. Note that this is different from an exit status.
61+
pub exit_code: i32,
62+
pub exit_signal: Option<String>,
63+
64+
pub verdict: Verdict,
65+
}
66+
67+
pub fn execute(payload: ExecuteRequest) -> Result<ExecuteResponse> {
2668
let tmp_dir = TempDir::new("execute")?;
2769

28-
match payload.executable {
70+
if let Some(ref name) = payload.options.file_io_name {
71+
if !name.chars().all(|c| c.is_ascii_alphanumeric()) {
72+
return Err(anyhow!("Invalid file I/O name. It must be alphanumeric, like \"cowdating\"."))
73+
}
74+
let mut stdin_file = File::create(tmp_dir.path().join(name).with_extension("in"))?;
75+
stdin_file.write_all(payload.options.stdin.as_ref())?;
76+
}
77+
78+
let command_options = CommandOptions { stdin: payload.options.stdin, timeout_ms: payload.options.timeout_ms };
79+
80+
let command_output = match payload.executable {
2981
Executable::Binary { value } => {
3082
let mut executable_file = File::create(tmp_dir.path().join("program"))?;
3183
executable_file.write_all(BASE64_STANDARD.decode(value)?.as_ref())?;
3284
executable_file.set_permissions(Permissions::from_mode(0o755))?;
3385
drop(executable_file);
3486

35-
run_command("./program", tmp_dir.path(), payload.options)
87+
run_command("./program", tmp_dir.path(), command_options)
3688
}
3789
Executable::JavaClass { class_name, value } => {
3890
let mut class_file = File::create(
@@ -47,7 +99,7 @@ pub fn execute(payload: ExecuteRequest) -> Result<CommandOutput> {
4799
run_command(
48100
format!("java {}", class_name).as_ref(),
49101
tmp_dir.path(),
50-
payload.options,
102+
command_options,
51103
)
52104
}
53105
Executable::Script {
@@ -63,13 +115,41 @@ pub fn execute(payload: ExecuteRequest) -> Result<CommandOutput> {
63115
executable_file.set_permissions(Permissions::from_mode(0o755))?;
64116
drop(executable_file);
65117

66-
run_command("python3.12 program.py", tmp_dir.path(), payload.options)
118+
run_command("python3.12 program.py", tmp_dir.path(), command_options)
67119
}
68-
}
120+
}?;
121+
122+
let verdict = match command_output.exit_code {
123+
124 => Verdict::TimeLimitExceeded,
124+
0 => Verdict::Accepted,
125+
_ => Verdict::RuntimeError,
126+
};
127+
128+
let file_output = if let Some(name) = payload.options.file_io_name {
129+
let output_file_path = tmp_dir.path().join(name).with_extension("out");
130+
if Path::exists(&output_file_path) {
131+
Some(String::from_utf8_lossy(&fs::read(output_file_path)?).into_owned())
132+
} else {
133+
None
134+
}
135+
} else {
136+
None
137+
};
138+
139+
Ok(ExecuteResponse {
140+
stdout: command_output.stdout,
141+
file_output,
142+
stderr: command_output.stderr,
143+
wall_time: command_output.wall_time,
144+
memory_usage: command_output.memory_usage,
145+
exit_code: command_output.exit_code,
146+
exit_signal: command_output.exit_signal,
147+
verdict,
148+
})
69149
}
70150

71151
pub async fn execute_handler(
72152
Json(payload): Json<ExecuteRequest>,
73-
) -> Result<Json<CommandOutput>, AppError> {
153+
) -> Result<Json<ExecuteResponse>, AppError> {
74154
Ok(Json(execute(payload)?))
75155
}

src/run_command.rs

-7
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,7 @@ pub struct CommandOutput {
2626

2727
/// The underlying raw wait status. Note that this is different from an exit status.
2828
pub exit_code: i32,
29-
3029
pub exit_signal: Option<String>,
31-
// /**
32-
// * When executing, if `fileIOName` is given, this is
33-
// * set to whatever is written in `[fileIOName].out`
34-
// * or null if there's no such file.
35-
// */
36-
// pub file_output: Option<String>,
3730
}
3831

3932
struct TimingOutput {

0 commit comments

Comments
 (0)