1use crate::shell::Shell;
2use anyhow::{bail, Context as _};
3use camino::Utf8Path;
4use itertools::Itertools as _;
5use serde::{
6 de::{DeserializeOwned, Error as _},
7 Deserialize, Deserializer,
8};
9use std::{env, ffi::OsStr, path::Path};
10use url::Url;
11
12pub(crate) fn get_problem(
13 url: &Url,
14 system: bool,
15 cwd: &Utf8Path,
16 shell: &mut Shell,
17) -> anyhow::Result<Problem> {
18 let args = &mut vec!["get-problem", url.as_ref()];
19 if system {
20 args.push("--system".as_ref());
21 }
22 call(args, cwd, shell)
23}
24
25pub(crate) fn get_contest(
26 url: &Url,
27 cwd: &Utf8Path,
28 shell: &mut Shell,
29) -> anyhow::Result<Vec<(Url, Option<String>)>> {
30 let Contest { problems } = call(&["get-contest", url.as_ref()], cwd, shell)?;
31 return Ok(problems
32 .into_iter()
33 .map(|ContestProblem { url, context }| (url, context.alphabet))
34 .collect());
35
36 #[derive(Deserialize)]
37 struct Contest {
38 problems: Vec<ContestProblem>,
39 }
40
41 #[derive(Deserialize)]
42 struct ContestProblem {
43 url: Url,
44 context: ContestProblemContext,
45 }
46
47 #[derive(Deserialize)]
48 struct ContestProblemContext {
49 alphabet: Option<String>,
50 }
51}
52
53pub(crate) fn guess_language_id(
54 url: &Url,
55 file: &Path,
56 cwd: &Utf8Path,
57 shell: &mut Shell,
58) -> anyhow::Result<String> {
59 return call(
60 &[
61 OsStr::new("guess-language-id"),
62 url.as_str().as_ref(),
63 "--file".as_ref(),
64 file.as_ref(),
65 ],
66 cwd,
67 shell,
68 )
69 .map(|GuessLanguageId { id }| id);
70
71 #[derive(Deserialize)]
72 struct GuessLanguageId {
73 id: String,
74 }
75}
76
77pub(crate) fn submit_code(
78 url: &Url,
79 file: &Path,
80 language: &str,
81 cwd: &Utf8Path,
82 shell: &mut Shell,
83) -> anyhow::Result<Url> {
84 return call(
85 &[
86 "submit-code".as_ref(),
87 url.as_str().as_ref(),
88 "--file".as_ref(),
89 file.as_os_str(),
90 "--language".as_ref(),
91 language.as_ref(),
92 ],
93 cwd,
94 shell,
95 )
96 .map(|SubmitCode { url }| url);
97
98 #[derive(Deserialize)]
99 struct SubmitCode {
100 url: Url,
101 }
102}
103
104fn call<T: DeserializeOwned, S: AsRef<OsStr>>(
105 args: &[S],
106 cwd: &Utf8Path,
107 shell: &mut Shell,
108) -> anyhow::Result<T> {
109 let oj_api_exe = which::which_in("oj-api", env::var_os("PATH"), cwd)
110 .with_context(|| "`oj-api` not found")?;
111
112 let output = crate::process::process(oj_api_exe)
113 .args(args)
114 .cwd(cwd)
115 .display_cwd()
116 .read_with_shell_status(shell)?;
117
118 let Outcome { result, messages } = serde_json::from_str(&output)
119 .with_context(|| "could not parse the output from `oj-api`")?;
120
121 return if let Ok(result) = result {
122 for message in messages {
123 shell.warn(format!("oj-api: {message}"))?;
124 }
125 Ok(result)
126 } else {
127 bail!(
128 "`oj-api` returned error:\n{}",
129 messages.iter().map(|s| format!("- {s}\n")).join(""),
130 );
131 };
132
133 struct Outcome<T> {
134 result: Result<T, ()>,
135 messages: Vec<String>,
136 }
137
138 impl<'de, T: DeserializeOwned> Deserialize<'de> for Outcome<T> {
139 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
140 where
141 D: Deserializer<'de>,
142 {
143 let Repr {
144 status,
145 messages,
146 result,
147 } = Repr::deserialize(deserializer)?;
148
149 return match &*status {
150 "ok" => Ok(Self {
151 result: Ok(result),
152 messages,
153 }),
154 "error" => Ok(Self {
155 result: Err(()),
156 messages,
157 }),
158 status => Err(D::Error::custom(format!(
159 "expected \"ok\" or \"error\", got {status:?}",
160 ))),
161 };
162
163 #[derive(Deserialize)]
164 struct Repr<T> {
165 status: String,
166 messages: Vec<String>,
167 result: T,
168 }
169 }
170 }
171}
172
173#[derive(Debug, Deserialize)]
174#[serde(rename_all = "camelCase")]
175pub(crate) struct Problem {
176 pub(crate) url: Url,
183
184 pub(crate) context: ProblemContext,
218
219 pub(crate) time_limit: Option<u64>,
226
227 pub(crate) tests: Vec<ProblemTest>,
263}
264
265#[derive(Debug, Deserialize)]
266pub(crate) struct ProblemContext {
267 pub(crate) contest: Option<ProblemContextContest>,
268 pub(crate) alphabet: Option<String>,
269}
270
271#[derive(Debug, Deserialize)]
272pub(crate) struct ProblemContextContest {
273 pub(crate) url: Option<Url>,
274 }
276
277#[derive(Debug, Deserialize)]
278pub(crate) struct ProblemTest {
279 pub(crate) name: Option<String>,
280 pub(crate) input: String,
281 pub(crate) output: String,
282}