use dropshot::endpoint;
use dropshot::ApiDescription;
use dropshot::ConfigLogging;
use dropshot::ConfigLoggingLevel;
use dropshot::ErrorStatusCode;
use dropshot::HttpError;
use dropshot::HttpResponseError;
use dropshot::HttpResponseOk;
use dropshot::Path;
use dropshot::RequestContext;
use dropshot::ServerBuilder;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug)]
#[derive(serde::Serialize, schemars::JsonSchema)]
#[derive(thiserror::Error)]
enum ThingyError {
#[error("no thingies are currently available")]
NoThingies,
#[error("invalid thingy: {:?}", .name)]
InvalidThingy { name: String },
#[error("{internal_message}")]
Other {
message: String,
error_code: Option<String>,
#[serde(skip)]
internal_message: String,
#[serde(skip)]
status: ErrorStatusCode,
},
}
impl HttpResponseError for ThingyError {
fn status_code(&self) -> dropshot::ErrorStatusCode {
match self {
ThingyError::NoThingies => {
dropshot::ErrorStatusCode::SERVICE_UNAVAILABLE
}
ThingyError::InvalidThingy { .. } => {
dropshot::ErrorStatusCode::from_u16(442)
.expect("442 is a 4xx status code")
}
ThingyError::Other { status, .. } => *status,
}
}
}
impl From<HttpError> for ThingyError {
fn from(error: HttpError) -> Self {
ThingyError::Other {
message: error.external_message,
internal_message: error.internal_message,
status: error.status_code,
error_code: error.error_code,
}
}
}
#[derive(Deserialize, Serialize, JsonSchema)]
struct Thingy {
magic_number: u64,
}
#[derive(Deserialize, JsonSchema)]
struct ThingyPathParams {
name: ThingyName,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
enum ThingyName {
Foo,
Bar,
}
#[endpoint {
method = GET,
path = "/thingy/{name}",
}]
async fn get_thingy(
_rqctx: RequestContext<()>,
path_params: Path<ThingyPathParams>,
) -> Result<HttpResponseOk<Thingy>, ThingyError> {
let ThingyPathParams { name } = path_params.into_inner();
Err(ThingyError::InvalidThingy { name: format!("{name:?}") })
}
#[endpoint {
method = GET,
path = "/nothing",
}]
async fn get_nothing(
_rqctx: RequestContext<()>,
) -> Result<HttpResponseOk<Thingy>, ThingyError> {
Err(ThingyError::NoThingies)
}
#[endpoint {
method = GET,
path = "/something",
}]
async fn get_something(
_rqctx: RequestContext<()>,
) -> Result<HttpResponseOk<Thingy>, dropshot::HttpError> {
Ok(HttpResponseOk(Thingy { magic_number: 42 }))
}
#[tokio::main]
async fn main() -> Result<(), String> {
let config_logging =
ConfigLogging::StderrTerminal { level: ConfigLoggingLevel::Info };
let log = config_logging
.to_logger("example-custom-error")
.map_err(|error| format!("failed to create logger: {}", error))?;
let mut api = ApiDescription::new();
api.register(get_thingy).unwrap();
api.register(get_nothing).unwrap();
api.register(get_something).unwrap();
api.openapi("Custom Error Example", semver::Version::new(0, 0, 0))
.write(&mut std::io::stdout())
.map_err(|e| e.to_string())?;
let server = ServerBuilder::new(api, (), log)
.start()
.map_err(|error| format!("failed to create server: {}", error))?;
server.await
}