feat(users/Profpatsch): add die_* helpers for semantic exit errors
There is this semantic exit code schema championed by execline and
skaware tooling, and we refined and documented it a bit in lorri
d1d673d420/src/ops/mod.rs (L24-L35)
in the past.
This just transcribes the error messages into simple helper functions.
Applies the functions to the places where we would panic or die
`sys::exit()` instead.
Change-Id: I15ca05cd6f99a25a3378518be94110eab416354e
Reviewed-on: https://cl.tvl.fyi/c/depot/+/2475
Tested-by: BuildkiteCI
Reviewed-by: Profpatsch <mail@profpatsch.de>
This commit is contained in:
parent
83634341aa
commit
492b79ec7a
5 changed files with 80 additions and 28 deletions
|
@ -36,6 +36,59 @@ pub fn exec_into_args<'a, 'b, Args, Arg, Env, Key, Val>(current_prog_name: &str,
|
||||||
let env = env_additions.into_iter().collect::<Vec<(Key, Val)>>();
|
let env = env_additions.into_iter().collect::<Vec<(Key, Val)>>();
|
||||||
let env = env.iter().map(|(k,v)| (OsStr::from_bytes(k.as_ref()), OsStr::from_bytes(v.as_ref())));
|
let env = env.iter().map(|(k,v)| (OsStr::from_bytes(k.as_ref()), OsStr::from_bytes(v.as_ref())));
|
||||||
let err = std::process::Command::new(prog).args(args).envs(env).exec();
|
let err = std::process::Command::new(prog).args(args).envs(env).exec();
|
||||||
panic!("{}: exec failed: {:?}", current_prog_name, err);
|
die_missing_executable(current_prog_name, format!("exec failed: {:?}", err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Exit 1 to signify a generic expected error
|
||||||
|
/// (e.g. something that sometimes just goes wrong, like a nix build).
|
||||||
|
pub fn die_expected_error<S>(current_prog_name: &str, msg: S) -> !
|
||||||
|
where S: AsRef<str>
|
||||||
|
{
|
||||||
|
die_with(1, current_prog_name, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit 100 to signify a user error (“the user is holding it wrong”).
|
||||||
|
/// This is a permanent error, if the program is executed the same way
|
||||||
|
/// it should crash with 100 again.
|
||||||
|
pub fn die_user_error<S>(current_prog_name: &str, msg: S) -> !
|
||||||
|
where S: AsRef<str>
|
||||||
|
{
|
||||||
|
die_with(100, current_prog_name, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit 101 to signify an unexpected crash (failing assertion or panic).
|
||||||
|
/// This is the same exit code that `panic!()` emits.
|
||||||
|
pub fn die_panic<S>(current_prog_name: &str, msg: S) -> !
|
||||||
|
where S: AsRef<str>
|
||||||
|
{
|
||||||
|
die_with(101, current_prog_name, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit 111 to signify a temporary error (such as resource exhaustion)
|
||||||
|
pub fn die_temporary<S>(current_prog_name: &str, msg: S) -> !
|
||||||
|
where S: AsRef<str>
|
||||||
|
{
|
||||||
|
die_with(111, current_prog_name, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit 126 to signify an environment problem
|
||||||
|
/// (the user has set up stuff incorrectly so the program cannot work)
|
||||||
|
pub fn die_environment_problem<S>(current_prog_name: &str, msg: S) -> !
|
||||||
|
where S: AsRef<str>
|
||||||
|
{
|
||||||
|
die_with(126, current_prog_name, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit 127 to signify a missing executable.
|
||||||
|
pub fn die_missing_executable<S>(current_prog_name: &str, msg: S) -> !
|
||||||
|
where S: AsRef<str>
|
||||||
|
{
|
||||||
|
die_with(127, current_prog_name, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn die_with<S>(status: i32, current_prog_name: &str, msg: S) -> !
|
||||||
|
where S: AsRef<str>
|
||||||
|
{
|
||||||
|
eprintln!("{}: {}", current_prog_name, msg.as_ref());
|
||||||
|
std::process::exit(status)
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,10 @@ let
|
||||||
|
|
||||||
netencode-rs-common = tests: imports.writers.rustSimpleLib {
|
netencode-rs-common = tests: imports.writers.rustSimpleLib {
|
||||||
name = "netencode";
|
name = "netencode";
|
||||||
dependencies = [ nom ];
|
dependencies = [
|
||||||
|
nom
|
||||||
|
depot.users.Profpatsch.execline.exec-helpers
|
||||||
|
];
|
||||||
buildTests = tests;
|
buildTests = tests;
|
||||||
release = false;
|
release = false;
|
||||||
verbose = true;
|
verbose = true;
|
||||||
|
@ -101,13 +104,13 @@ let
|
||||||
use netencode::dec::{Record, ScalarAsBytes, Decoder, DecodeError};
|
use netencode::dec::{Record, ScalarAsBytes, Decoder, DecodeError};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let t = netencode::t_from_stdin_or_panic("record-splice-env");
|
let t = netencode::t_from_stdin_or_die_user_error("record-splice-env");
|
||||||
let (_, prog) = exec_helpers::args_for_exec("record-splice-env", 0);
|
let (_, prog) = exec_helpers::args_for_exec("record-splice-env", 0);
|
||||||
match Record::<ScalarAsBytes>::dec(t) {
|
match Record::<ScalarAsBytes>::dec(t) {
|
||||||
Ok(map) => {
|
Ok(map) => {
|
||||||
exec_helpers::exec_into_args("record-splice-env", prog, map);
|
exec_helpers::exec_into_args("record-splice-env", prog, map);
|
||||||
},
|
},
|
||||||
Err(DecodeError(err)) => panic!("{}", err),
|
Err(DecodeError(err)) => exec_helpers::die_user_error("record-splice-env", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
extern crate nom;
|
extern crate nom;
|
||||||
|
extern crate exec_helpers;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::{Write, Read};
|
use std::io::{Write, Read};
|
||||||
|
@ -116,15 +117,15 @@ pub fn text(s: String) -> T {
|
||||||
T::Text(s)
|
T::Text(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn t_from_stdin_or_panic(prog_name: &str) -> T {
|
pub fn t_from_stdin_or_die_user_error(prog_name: &str) -> T {
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
std::io::stdin().lock().read_to_end(&mut buf);
|
std::io::stdin().lock().read_to_end(&mut buf);
|
||||||
match parse::t_t(&buf) {
|
match parse::t_t(&buf) {
|
||||||
Ok((rest, t)) => match rest {
|
Ok((rest, t)) => match rest {
|
||||||
b"" => t,
|
b"" => t,
|
||||||
_ => panic!("{}: stdin contained some soup after netencode value: {:?}", prog_name, rest)
|
_ => exec_helpers::die_user_error(prog_name, format!("stdin contained some soup after netencode value: {:?}", rest))
|
||||||
},
|
},
|
||||||
Err(err) => panic!("{}: unable to parse netencode from stdin: {:?}", prog_name, err)
|
Err(err) => exec_helpers::die_user_error(prog_name, format!("unable to parse netencode from stdin: {:?}", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,8 @@ let
|
||||||
depot.users.Profpatsch.rust-crates.httparse
|
depot.users.Profpatsch.rust-crates.httparse
|
||||||
depot.users.Profpatsch.netencode.netencode-rs
|
depot.users.Profpatsch.netencode.netencode-rs
|
||||||
depot.users.Profpatsch.arglib.netencode.rust
|
depot.users.Profpatsch.arglib.netencode.rust
|
||||||
|
depot.users.Profpatsch.execline.exec-helpers
|
||||||
];
|
];
|
||||||
} (builtins.readFile ./read-http.rs);
|
} (builtins.readFile ./read-http.rs);
|
||||||
|
|
||||||
in {
|
in read-http
|
||||||
inherit
|
|
||||||
read-http
|
|
||||||
;
|
|
||||||
}
|
|
|
@ -2,10 +2,12 @@ extern crate httparse;
|
||||||
extern crate netencode;
|
extern crate netencode;
|
||||||
extern crate arglib_netencode;
|
extern crate arglib_netencode;
|
||||||
extern crate ascii;
|
extern crate ascii;
|
||||||
|
extern crate exec_helpers;
|
||||||
|
|
||||||
use std::os::unix::io::FromRawFd;
|
use std::os::unix::io::FromRawFd;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use exec_helpers::{die_user_error, die_expected_error, die_temporary};
|
||||||
|
|
||||||
use netencode::{U, T};
|
use netencode::{U, T};
|
||||||
|
|
||||||
|
@ -15,25 +17,21 @@ enum What {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
fn die<T: std::fmt::Display>(msg: T) -> ! {
|
|
||||||
eprintln!("{}", msg);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let what : What = match arglib_netencode::arglib_netencode(None).unwrap() {
|
let what : What = match arglib_netencode::arglib_netencode(None).unwrap() {
|
||||||
T::Record(rec) => match rec.get("what") {
|
T::Record(rec) => match rec.get("what") {
|
||||||
Some(T::Text(t)) => match t.as_str() {
|
Some(T::Text(t)) => match t.as_str() {
|
||||||
"request" => What::Request,
|
"request" => What::Request,
|
||||||
"response" => What::Response,
|
"response" => What::Response,
|
||||||
_ => die("read-http arglib: what should be either t:request or t:response"),
|
_ => die_user_error("read-http arglib", "`what` should be either t:request or t:response"),
|
||||||
},
|
},
|
||||||
Some(o) => die(format!("read-http arglib: expected a record of text, got {:#?}", o)),
|
Some(o) => die_user_error("read-http arglib", format!("expected a record of text, got {:#?}", o)),
|
||||||
None => {
|
None => {
|
||||||
eprintln!("read-http arglib: no `what` given, defaulting to Response");
|
eprintln!("read-http arglib: no `what` given, defaulting to Response");
|
||||||
What::Response
|
What::Response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
o => die(format!("read-http arglib: expected a record, got {:#?}", o))
|
o => die_user_error("read-http arglib", format!("expected a record, got {:#?}", o))
|
||||||
};
|
};
|
||||||
|
|
||||||
fn read_stdin_to_complete<F>(mut parse: F) -> ()
|
fn read_stdin_to_complete<F>(mut parse: F) -> ()
|
||||||
|
@ -49,13 +47,13 @@ fn main() -> std::io::Result<()> {
|
||||||
Ok(size) => if size == 0 {
|
Ok(size) => if size == 0 {
|
||||||
break;
|
break;
|
||||||
},
|
},
|
||||||
Err(err) => panic!("could not read from stdin, {:?}", err)
|
Err(err) => die_temporary("read-http", format!("could not read from stdin, {:?}", err))
|
||||||
}
|
}
|
||||||
match parse(&buf) {
|
match parse(&buf) {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
res = status;
|
res = status;
|
||||||
}
|
}
|
||||||
Err(err) => die(format!("httparse parsing failed: {:#?}", err))
|
Err(err) => die_temporary("read-http", format!("httparse parsing failed: {:#?}", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +82,7 @@ fn main() -> std::io::Result<()> {
|
||||||
return Some(());
|
return Some(());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(Err(err)) => die(format!("error reading from stdin: {:?}", err)),
|
Some(Err(err)) => die_temporary("read-http", format!("error reading from stdin: {:?}", err)),
|
||||||
None => return None
|
None => return None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,10 +99,10 @@ fn main() -> std::io::Result<()> {
|
||||||
match read_till_end_of_header(&mut buf, stdin.lock()) {
|
match read_till_end_of_header(&mut buf, stdin.lock()) {
|
||||||
Some(()) => match req.parse(&buf) {
|
Some(()) => match req.parse(&buf) {
|
||||||
Ok(httparse::Status::Complete(_body_start)) => {},
|
Ok(httparse::Status::Complete(_body_start)) => {},
|
||||||
Ok(httparse::Status::Partial) => die("httparse should have gotten a full header"),
|
Ok(httparse::Status::Partial) => die_expected_error("read-http", "httparse should have gotten a full header"),
|
||||||
Err(err) => die(format!("httparse response parsing failed: {:#?}", err))
|
Err(err) => die_expected_error("read-http", format!("httparse response parsing failed: {:#?}", err))
|
||||||
},
|
},
|
||||||
None => die(format!("httparse end of stdin reached before able to parse request headers"))
|
None => die_expected_error("read-http", format!("httparse end of stdin reached before able to parse request headers"))
|
||||||
}
|
}
|
||||||
let method = req.method.expect("method must be filled on complete parse");
|
let method = req.method.expect("method must be filled on complete parse");
|
||||||
let path = req.path.expect("path must be filled on complete parse");
|
let path = req.path.expect("path must be filled on complete parse");
|
||||||
|
@ -116,10 +114,10 @@ fn main() -> std::io::Result<()> {
|
||||||
match read_till_end_of_header(&mut buf, stdin.lock()) {
|
match read_till_end_of_header(&mut buf, stdin.lock()) {
|
||||||
Some(()) => match resp.parse(&buf) {
|
Some(()) => match resp.parse(&buf) {
|
||||||
Ok(httparse::Status::Complete(_body_start)) => {},
|
Ok(httparse::Status::Complete(_body_start)) => {},
|
||||||
Ok(httparse::Status::Partial) => die("httparse should have gotten a full header"),
|
Ok(httparse::Status::Partial) => die_expected_error("read-http", "httparse should have gotten a full header"),
|
||||||
Err(err) => die(format!("httparse response parsing failed: {:#?}", err))
|
Err(err) => die_expected_error("read-http", format!("httparse response parsing failed: {:#?}", err))
|
||||||
},
|
},
|
||||||
None => die(format!("httparse end of stdin reached before able to parse response headers"))
|
None => die_expected_error("read-http", format!("httparse end of stdin reached before able to parse response headers"))
|
||||||
}
|
}
|
||||||
let code = resp.code.expect("code must be filled on complete parse");
|
let code = resp.code.expect("code must be filled on complete parse");
|
||||||
let reason = resp.reason.expect("reason must be filled on complete parse");
|
let reason = resp.reason.expect("reason must be filled on complete parse");
|
Loading…
Add table
Reference in a new issue