use std::time::Duration;
use shep_client::{
Client, LOG_PLANE_DEADLINE, RELOAD_DEADLINE, RequestError, START_DEADLINE, STOP_DEADLINE,
TRIGGER_DEADLINE,
};
use shep_core::protocol::{Request, Response};
use crate::exit::ExitCode;
use crate::output::{Render, Streams, emit, write_outcome};
pub(crate) const UNRECOGNISED: &str =
"the daemon answered with a response this client does not understand";
pub(crate) const fn deadline_for(body: &Request) -> Option<Duration> {
match body {
Request::Stop { .. } | Request::Delete { .. } => Some(STOP_DEADLINE),
Request::Start { .. } | Request::Restart { .. } => Some(START_DEADLINE),
Request::Reload { .. } => Some(RELOAD_DEADLINE),
Request::Trigger { .. } => Some(TRIGGER_DEADLINE),
Request::Reopen { .. } | Request::Flush { .. } => Some(LOG_PLANE_DEADLINE),
_ => None,
}
}
pub(crate) fn unexpected_response(streams: &mut Streams<'_>) -> ExitCode {
streams.fail(ExitCode::Internal, UNRECOGNISED)
}
pub(crate) fn client_error(streams: &mut Streams<'_>, err: &RequestError) -> ExitCode {
streams.fail(ExitCode::from(err), &err.to_string())
}
pub(crate) fn typed_request_error(streams: &mut Streams<'_>, err: &RequestError) -> ExitCode {
match err {
RequestError::UnexpectedReply { .. } => unexpected_response(streams),
other => client_error(streams, other),
}
}
pub(crate) async fn request_payload<T, F>(
client: &Client,
streams: &mut Streams<'_>,
body: Request,
deadline: Option<Duration>,
extract: F,
) -> Result<T, ExitCode>
where
F: FnOnce(Response) -> Option<T>,
{
match client.request_with_deadline(body, deadline).await {
Ok(response) => extract(response).ok_or_else(|| unexpected_response(streams)),
Err(err) => Err(client_error(streams, &err)),
}
}
pub(crate) async fn request_and_render<T, F>(
client: &Client,
streams: &mut Streams<'_>,
command: &str,
body: Request,
deadline: Option<Duration>,
extract: F,
) -> ExitCode
where
T: Render,
F: FnOnce(Response) -> Option<T>,
{
match request_payload(client, streams, body, deadline, extract).await {
Ok(payload) => write_outcome(emit(
&mut *streams.out,
streams.fmt,
command,
payload,
streams.style,
)),
Err(code) => code,
}
}
#[cfg(test)]
mod tests {
use super::*;
use shep_client::{
LOG_PLANE_DEADLINE, RELOAD_DEADLINE, START_DEADLINE, STOP_DEADLINE, TRIGGER_DEADLINE,
};
use shep_core::protocol::SelectorSpec;
#[test]
fn each_lifecycle_verb_names_its_own_budget() {
let selector = || SelectorSpec::All;
let cases = [
(
Request::Stop {
selector: selector(),
},
Some(STOP_DEADLINE),
),
(
Request::Delete {
selector: selector(),
},
Some(STOP_DEADLINE),
),
(Request::Start { apps: Vec::new() }, Some(START_DEADLINE)),
(
Request::Restart {
selector: selector(),
},
Some(START_DEADLINE),
),
(
Request::Reload {
selector: selector(),
},
Some(RELOAD_DEADLINE),
),
(
Request::Trigger {
selector: selector(),
action: "x".into(),
params: None,
},
Some(TRIGGER_DEADLINE),
),
(
Request::Reopen {
selector: selector(),
},
Some(LOG_PLANE_DEADLINE),
),
(
Request::Flush {
selector: selector(),
},
Some(LOG_PLANE_DEADLINE),
),
(Request::Ping, None),
(Request::ListFlock, None),
];
for (request, expected) in cases {
assert_eq!(deadline_for(&request), expected, "{request:?}");
}
}
}