From 3f7ff6843d706056edd09f0f3bdc1c35fd099f38 Mon Sep 17 00:00:00 2001 From: Tpt Date: Fri, 25 Nov 2022 21:59:03 +0100 Subject: [PATCH] Server: simplifies error related code --- server/src/main.rs | 467 ++++++++++++++++++--------------------------- 1 file changed, 186 insertions(+), 281 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index 49f593c0..e5eb025b 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -6,7 +6,7 @@ use oxhttp::Server; use oxigraph::io::{DatasetFormat, DatasetSerializer, GraphFormat, GraphSerializer}; use oxigraph::model::{GraphName, GraphNameRef, IriParseError, NamedNode, NamedOrBlankNode}; use oxigraph::sparql::{Query, QueryResults, Update}; -use oxigraph::store::{BulkLoader, Store}; +use oxigraph::store::{BulkLoader, LoaderError, Store}; use oxiri::Iri; use rand::random; use rayon_core::ThreadPoolBuilder; @@ -137,7 +137,10 @@ pub fn main() -> anyhow::Result<()> { Ok(()) } Command::Serve { bind } => { - let mut server = Server::new(move |request| handle_request(request, store.clone())); + let mut server = Server::new(move |request| { + handle_request(request, store.clone()) + .unwrap_or_else(|(status, message)| error(status, message)) + }); server.set_global_timeout(HTTP_TIMEOUT); server .set_server_name(concat!("Oxigraph/", env!("CARGO_PKG_VERSION"))) @@ -154,7 +157,7 @@ fn bulk_load( reader: impl Read, format: GraphOrDatasetFormat, base_iri: Option<&str>, -) -> anyhow::Result<()> { +) -> Result<(), LoaderError> { let reader = BufReader::new(reader); match format { GraphOrDatasetFormat::Graph(format) => { @@ -217,24 +220,26 @@ impl GraphOrDatasetFormat { } } -fn handle_request(request: &mut Request, store: Store) -> Response { +type HttpError = (Status, String); + +fn handle_request(request: &mut Request, store: Store) -> Result { match (request.url().path(), request.method().as_ref()) { - ("/", "HEAD") => Response::builder(Status::OK) + ("/", "HEAD") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "text_html") .unwrap() - .build(), - ("/", "GET") => Response::builder(Status::OK) + .build()), + ("/", "GET") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "text_html") .unwrap() - .with_body(HTML_ROOT_PAGE), - ("/logo.svg", "HEAD") => Response::builder(Status::OK) + .with_body(HTML_ROOT_PAGE)), + ("/logo.svg", "HEAD") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "image/svg+xml") .unwrap() - .build(), - ("/logo.svg", "GET") => Response::builder(Status::OK) + .build()), + ("/logo.svg", "GET") => Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, "image/svg+xml") .unwrap() - .with_body(LOGO), + .with_body(LOGO)), ("/query", "GET") => { configure_and_evaluate_sparql_query(store, &[url_query(request)], None, request) } @@ -242,13 +247,11 @@ fn handle_request(request: &mut Request, store: Store) -> Response { if let Some(content_type) = content_type(request) { if content_type == "application/sparql-query" { let mut buffer = String::new(); - if let Err(e) = request + request .body_mut() .take(MAX_SPARQL_BODY_SIZE) .read_to_string(&mut buffer) - { - return bad_request(e); - } + .map_err(bad_request)?; configure_and_evaluate_sparql_query( store, &[url_query(request)], @@ -257,13 +260,11 @@ fn handle_request(request: &mut Request, store: Store) -> Response { ) } else if content_type == "application/x-www-form-urlencoded" { let mut buffer = Vec::new(); - if let Err(e) = request + request .body_mut() .take(MAX_SPARQL_BODY_SIZE) .read_to_end(&mut buffer) - { - return bad_request(e); - } + .map_err(bad_request)?; configure_and_evaluate_sparql_query( store, &[url_query(request), &buffer], @@ -271,23 +272,21 @@ fn handle_request(request: &mut Request, store: Store) -> Response { request, ) } else { - unsupported_media_type(&content_type) + Err(unsupported_media_type(&content_type)) } } else { - bad_request("No Content-Type given") + Err(bad_request("No Content-Type given")) } } ("/update", "POST") => { if let Some(content_type) = content_type(request) { if content_type == "application/sparql-update" { let mut buffer = String::new(); - if let Err(e) = request + request .body_mut() .take(MAX_SPARQL_BODY_SIZE) .read_to_string(&mut buffer) - { - return bad_request(e); - } + .map_err(bad_request)?; configure_and_evaluate_sparql_update( store, &[url_query(request)], @@ -296,13 +295,11 @@ fn handle_request(request: &mut Request, store: Store) -> Response { ) } else if content_type == "application/x-www-form-urlencoded" { let mut buffer = Vec::new(); - if let Err(e) = request + request .body_mut() .take(MAX_SPARQL_BODY_SIZE) .read_to_end(&mut buffer) - { - return bad_request(e); - } + .map_err(bad_request)?; configure_and_evaluate_sparql_update( store, &[url_query(request), &buffer], @@ -310,33 +307,26 @@ fn handle_request(request: &mut Request, store: Store) -> Response { request, ) } else { - unsupported_media_type(&content_type) + return Err(unsupported_media_type(&content_type)); } } else { - bad_request("No Content-Type given") + Err(bad_request("No Content-Type given")) } } (path, "GET") if path.starts_with("/store") => { - if let Some(target) = match store_target(request) { - Ok(target) => target, - Err(error) => return error, - } { + if let Some(target) = store_target(request)? { if !match &target { NamedGraphName::DefaultGraph => true, - NamedGraphName::NamedNode(target) => match store.contains_named_graph(target) { - Ok(r) => r, - Err(e) => return internal_server_error(e), - }, + NamedGraphName::NamedNode(target) => store + .contains_named_graph(target) + .map_err(internal_server_error)?, } { - return error( + return Err(( Status::NOT_FOUND, format!("The graph {} does not exists", GraphName::from(target)), - ); + )); } - let format = match graph_content_negotiation(request) { - Ok(format) => format, - Err(response) => return response, - }; + let format = graph_content_negotiation(request)?; let triples = store.quads_for_pattern( None, None, @@ -362,10 +352,7 @@ fn handle_request(request: &mut Request, store: Store) -> Response { format.media_type(), ) } else { - let format = match dataset_content_negotiation(request) { - Ok(format) => format, - Err(response) => return response, - }; + let format = dataset_content_negotiation(request)?; ReadForWrite::build_response( move |w| { Ok(( @@ -388,198 +375,164 @@ fn handle_request(request: &mut Request, store: Store) -> Response { } (path, "PUT") if path.starts_with("/store") => { if let Some(content_type) = content_type(request) { - if let Some(target) = match store_target(request) { - Ok(target) => target, - Err(error) => return error, - } { + if let Some(target) = store_target(request)? { if let Some(format) = GraphFormat::from_media_type(&content_type) { let new = !match &target { NamedGraphName::NamedNode(target) => { - if match store.contains_named_graph(target) { - Ok(r) => r, - Err(e) => return internal_server_error(e), - } { - if let Err(e) = store.clear_graph(target) { - return internal_server_error(e); - } + if store + .contains_named_graph(target) + .map_err(internal_server_error)? + { + store.clear_graph(target).map_err(internal_server_error)?; true } else { - if let Err(e) = store.insert_named_graph(target) { - return internal_server_error(e); - } + store + .insert_named_graph(target) + .map_err(internal_server_error)?; false } } NamedGraphName::DefaultGraph => { - if let Err(e) = store.clear_graph(GraphNameRef::DefaultGraph) { - return internal_server_error(e); - } + store + .clear_graph(GraphNameRef::DefaultGraph) + .map_err(internal_server_error)?; true } }; - if let Err(e) = store.load_graph( - BufReader::new(request.body_mut()), - format, - GraphName::from(target).as_ref(), - None, - ) { - return bad_request(e); - } - Response::builder(if new { + store + .load_graph( + BufReader::new(request.body_mut()), + format, + GraphName::from(target).as_ref(), + None, + ) + .map_err(bad_request)?; + Ok(Response::builder(if new { Status::CREATED } else { Status::NO_CONTENT }) - .build() + .build()) } else { - unsupported_media_type(&content_type) + Err(unsupported_media_type(&content_type)) } } else if let Some(format) = DatasetFormat::from_media_type(&content_type) { - if let Err(e) = store.clear() { - return internal_server_error(e); - } - if let Err(e) = - store.load_dataset(BufReader::new(request.body_mut()), format, None) - { - return internal_server_error(e); - } - Response::builder(Status::NO_CONTENT).build() + store.clear().map_err(internal_server_error)?; + store + .load_dataset(BufReader::new(request.body_mut()), format, None) + .map_err(internal_server_error)?; + Ok(Response::builder(Status::NO_CONTENT).build()) } else { - unsupported_media_type(&content_type) + Err(unsupported_media_type(&content_type)) } } else { - bad_request("No Content-Type given") + Err(bad_request("No Content-Type given")) } } (path, "DELETE") if path.starts_with("/store") => { - if let Some(target) = match store_target(request) { - Ok(target) => target, - Err(error) => return error, - } { + if let Some(target) = store_target(request)? { match target { - NamedGraphName::DefaultGraph => { - if let Err(e) = store.clear_graph(GraphNameRef::DefaultGraph) { - return internal_server_error(e); - } - } + NamedGraphName::DefaultGraph => store + .clear_graph(GraphNameRef::DefaultGraph) + .map_err(internal_server_error)?, NamedGraphName::NamedNode(target) => { - if match store.contains_named_graph(&target) { - Ok(r) => r, - Err(e) => return internal_server_error(e), - } { - if let Err(e) = store.remove_named_graph(&target) { - return internal_server_error(e); - } + if store + .contains_named_graph(&target) + .map_err(internal_server_error)? + { + store + .remove_named_graph(&target) + .map_err(internal_server_error)?; } else { - return error( + return Err(( Status::NOT_FOUND, format!("The graph {} does not exists", target), - ); + )); } } } - } else if let Err(e) = store.clear() { - return internal_server_error(e); + } else { + store.clear().map_err(internal_server_error)?; } - Response::builder(Status::NO_CONTENT).build() + Ok(Response::builder(Status::NO_CONTENT).build()) } (path, "POST") if path.starts_with("/store") => { if let Some(content_type) = content_type(request) { - if let Some(target) = match store_target(request) { - Ok(target) => target, - Err(error) => return error, - } { + if let Some(target) = store_target(request)? { if let Some(format) = GraphFormat::from_media_type(&content_type) { let new = !match &target { - NamedGraphName::NamedNode(target) => { - match store.contains_named_graph(target) { - Ok(r) => r, - Err(e) => return internal_server_error(e), - } - } + NamedGraphName::NamedNode(target) => store + .contains_named_graph(target) + .map_err(internal_server_error)?, NamedGraphName::DefaultGraph => true, }; - if let Err(e) = store.load_graph( - BufReader::new(request.body_mut()), - format, - GraphName::from(target).as_ref(), - None, - ) { - return bad_request(e); - } - Response::builder(if new { + store + .load_graph( + BufReader::new(request.body_mut()), + format, + GraphName::from(target).as_ref(), + None, + ) + .map_err(bad_request)?; + Ok(Response::builder(if new { Status::CREATED } else { Status::NO_CONTENT }) - .build() + .build()) } else { - unsupported_media_type(&content_type) + Err(unsupported_media_type(&content_type)) } } else if let Some(format) = DatasetFormat::from_media_type(&content_type) { - if let Err(e) = - store.load_dataset(BufReader::new(request.body_mut()), format, None) - { - return bad_request(e); - } - Response::builder(Status::NO_CONTENT).build() + store + .load_dataset(BufReader::new(request.body_mut()), format, None) + .map_err(bad_request)?; + Ok(Response::builder(Status::NO_CONTENT).build()) } else if let Some(format) = GraphFormat::from_media_type(&content_type) { let graph = - match resolve_with_base(request, &format!("/store/{:x}", random::())) - { - Ok(graph) => graph, - Err(e) => return e, - }; - if let Err(e) = - store.load_graph(BufReader::new(request.body_mut()), format, &graph, None) - { - return bad_request(e); - } - Response::builder(Status::CREATED) + resolve_with_base(request, &format!("/store/{:x}", random::()))?; + store + .load_graph(BufReader::new(request.body_mut()), format, &graph, None) + .map_err(bad_request)?; + Ok(Response::builder(Status::CREATED) .with_header(HeaderName::LOCATION, graph.into_string()) .unwrap() - .build() + .build()) } else { - unsupported_media_type(&content_type) + Err(unsupported_media_type(&content_type)) } } else { - bad_request("No Content-Type given") + Err(bad_request("No Content-Type given")) } } (path, "HEAD") if path.starts_with("/store") => { - if let Some(target) = match store_target(request) { - Ok(target) => target, - Err(error) => return error, - } { + if let Some(target) = store_target(request)? { if !match &target { NamedGraphName::DefaultGraph => true, - NamedGraphName::NamedNode(target) => match store.contains_named_graph(target) { - Ok(r) => r, - Err(e) => return internal_server_error(e), - }, + NamedGraphName::NamedNode(target) => store + .contains_named_graph(target) + .map_err(internal_server_error)?, } { - return error( + return Err(( Status::NOT_FOUND, format!("The graph {} does not exists", GraphName::from(target)), - ); + )); } - Response::builder(Status::OK).build() - } else { - Response::builder(Status::OK).build() } + Ok(Response::builder(Status::OK).build()) } - _ => error( + _ => Err(( Status::NOT_FOUND, format!( "{} {} is not supported by this server", request.method(), request.url().path() ), - ), + )), } } -fn base_url(request: &Request) -> Result { +fn base_url(request: &Request) -> Result { let mut url = request.url().clone(); if let Some(host) = request.url().host_str() { url.set_host(Some(host)).map_err(bad_request)?; @@ -589,7 +542,7 @@ fn base_url(request: &Request) -> Result { Ok(url.into()) } -fn resolve_with_base(request: &Request, url: &str) -> Result { +fn resolve_with_base(request: &Request, url: &str) -> Result { Ok(NamedNode::new_unchecked( Iri::parse(base_url(request)?) .map_err(bad_request)? @@ -608,7 +561,7 @@ fn configure_and_evaluate_sparql_query( encoded: &[&[u8]], mut query: Option, request: &Request, -) -> Response { +) -> Result { let mut default_graph_uris = Vec::new(); let mut named_graph_uris = Vec::new(); let mut use_default_graph_as_union = false; @@ -617,7 +570,7 @@ fn configure_and_evaluate_sparql_query( match k.as_ref() { "query" => { if query.is_some() { - return bad_request("Multiple query parameters provided"); + return Err(bad_request("Multiple query parameters provided")); } query = Some(v.into_owned()) } @@ -638,7 +591,7 @@ fn configure_and_evaluate_sparql_query( request, ) } else { - bad_request("You should set the 'query' parameter") + Err(bad_request("You should set the 'query' parameter")) } } @@ -649,58 +602,37 @@ fn evaluate_sparql_query( default_graph_uris: Vec, named_graph_uris: Vec, request: &Request, -) -> Response { - let mut query = match Query::parse( - &query, - Some(&match base_url(request) { - Ok(url) => url, - Err(r) => return r, - }), - ) { - Ok(query) => query, - Err(e) => return bad_request(e), - }; +) -> Result { + let mut query = Query::parse(&query, Some(&base_url(request)?)).map_err(bad_request)?; if use_default_graph_as_union { if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() { - return bad_request( + return Err(bad_request( "default-graph-uri or named-graph-uri and union-default-graph should not be set at the same time" - ); + )); } query.dataset_mut().set_default_graph_as_union() } else if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() { query.dataset_mut().set_default_graph( - match default_graph_uris + default_graph_uris .into_iter() .map(|e| Ok(NamedNode::new(e)?.into())) .collect::, IriParseError>>() - { - Ok(default_graph_uris) => default_graph_uris, - Err(e) => return bad_request(e), - }, + .map_err(bad_request)?, ); query.dataset_mut().set_available_named_graphs( - match named_graph_uris + named_graph_uris .into_iter() .map(|e| Ok(NamedNode::new(e)?.into())) .collect::, IriParseError>>() - { - Ok(named_graph_uris) => named_graph_uris, - Err(e) => return bad_request(e), - }, + .map_err(bad_request)?, ); } - let results = match store.query(query) { - Ok(results) => results, - Err(e) => return internal_server_error(e), - }; + let results = store.query(query).map_err(internal_server_error)?; match results { QueryResults::Solutions(solutions) => { - let format = match query_results_content_negotiation(request) { - Ok(format) => format, - Err(response) => return response, - }; + let format = query_results_content_negotiation(request)?; ReadForWrite::build_response( move |w| { Ok(( @@ -722,26 +654,18 @@ fn evaluate_sparql_query( ) } QueryResults::Boolean(result) => { - let format = match query_results_content_negotiation(request) { - Ok(format) => format, - Err(response) => return response, - }; + let format = query_results_content_negotiation(request)?; let mut body = Vec::new(); - if let Err(e) = - QueryResultsSerializer::from_format(format).write_boolean_result(&mut body, result) - { - return internal_server_error(e); - } - Response::builder(Status::OK) + QueryResultsSerializer::from_format(format) + .write_boolean_result(&mut body, result) + .map_err(internal_server_error)?; + Ok(Response::builder(Status::OK) .with_header(HeaderName::CONTENT_TYPE, format.media_type()) .unwrap() - .with_body(body) + .with_body(body)) } QueryResults::Graph(triples) => { - let format = match graph_content_negotiation(request) { - Ok(format) => format, - Err(response) => return response, - }; + let format = graph_content_negotiation(request)?; ReadForWrite::build_response( move |w| { Ok(( @@ -769,7 +693,7 @@ fn configure_and_evaluate_sparql_update( encoded: &[&[u8]], mut update: Option, request: &Request, -) -> Response { +) -> Result { let mut use_default_graph_as_union = false; let mut default_graph_uris = Vec::new(); let mut named_graph_uris = Vec::new(); @@ -778,7 +702,7 @@ fn configure_and_evaluate_sparql_update( match k.as_ref() { "update" => { if update.is_some() { - return bad_request("Multiple update parameters provided"); + return Err(bad_request("Multiple update parameters provided")); } update = Some(v.into_owned()) } @@ -799,7 +723,7 @@ fn configure_and_evaluate_sparql_update( request, ) } else { - bad_request("You should set the 'update' parameter") + Err(bad_request("You should set the 'update' parameter")) } } @@ -810,69 +734,50 @@ fn evaluate_sparql_update( default_graph_uris: Vec, named_graph_uris: Vec, request: &Request, -) -> Response { - let mut update = match Update::parse( - &update, - Some( - match base_url(request) { - Ok(url) => url, - Err(e) => return e, - } - .as_str(), - ), - ) { - Ok(update) => update, - Err(e) => return bad_request(e), - }; +) -> Result { + let mut update = + Update::parse(&update, Some(base_url(request)?.as_str())).map_err(bad_request)?; if use_default_graph_as_union { if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() { - return bad_request( + return Err(bad_request( "using-graph-uri or using-named-graph-uri and using-union-graph should not be set at the same time" - ); + )); } for using in update.using_datasets_mut() { if !using.is_default_dataset() { - return bad_request( + return Err(bad_request( "using-union-graph must not be used with a SPARQL UPDATE containing USING", - ); + )); } using.set_default_graph_as_union(); } } else if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() { - let default_graph_uris = match default_graph_uris + let default_graph_uris = default_graph_uris .into_iter() .map(|e| Ok(NamedNode::new(e)?.into())) .collect::, IriParseError>>() - { - Ok(default_graph_uris) => default_graph_uris, - Err(e) => return bad_request(e), - }; - let named_graph_uris = match named_graph_uris + .map_err(bad_request)?; + let named_graph_uris = named_graph_uris .into_iter() .map(|e| Ok(NamedNode::new(e)?.into())) .collect::, IriParseError>>() - { - Ok(named_graph_uris) => named_graph_uris, - Err(e) => return bad_request(e), - }; + .map_err(bad_request)?; for using in update.using_datasets_mut() { if !using.is_default_dataset() { - return bad_request( + return Err(bad_request( "using-graph-uri and using-named-graph-uri must not be used with a SPARQL UPDATE containing USING", - ); + )); } using.set_default_graph(default_graph_uris.clone()); using.set_available_named_graphs(named_graph_uris.clone()); } } - if let Err(e) = store.update(update) { - return internal_server_error(e); - } - Response::builder(Status::NO_CONTENT).build() + store.update(update).map_err(internal_server_error)?; + Ok(Response::builder(Status::NO_CONTENT).build()) } -fn store_target(request: &Request) -> Result, Response> { +fn store_target(request: &Request) -> Result, HttpError> { if request.url().path() == "/store" { let mut graph = None; let mut default = false; @@ -922,7 +827,7 @@ impl From for GraphName { } } -fn graph_content_negotiation(request: &Request) -> Result { +fn graph_content_negotiation(request: &Request) -> Result { content_negotiation( request, &[ @@ -934,7 +839,7 @@ fn graph_content_negotiation(request: &Request) -> Result ) } -fn dataset_content_negotiation(request: &Request) -> Result { +fn dataset_content_negotiation(request: &Request) -> Result { content_negotiation( request, &[ @@ -945,7 +850,7 @@ fn dataset_content_negotiation(request: &Request) -> Result Result { +fn query_results_content_negotiation(request: &Request) -> Result { content_negotiation( request, &[ @@ -962,7 +867,7 @@ fn content_negotiation( request: &Request, supported: &[&str], parse: impl Fn(&str) -> Option, -) -> Result { +) -> Result { let default = HeaderValue::default(); let header = request .header(&HeaderName::ACCEPT) @@ -1015,13 +920,13 @@ fn content_negotiation( } let result = result.ok_or_else(|| { - error( + ( Status::NOT_ACCEPTABLE, format!("The available Content-Types are {}", supported.join(", "),), ) })?; - parse(result).ok_or_else(|| error(Status::INTERNAL_SERVER_ERROR, "Unknown media type")) + parse(result).ok_or_else(|| internal_server_error("Unknown media type")) } fn content_type(request: &Request) -> Option { @@ -1042,20 +947,20 @@ fn error(status: Status, message: impl fmt::Display) -> Response { .with_body(message.to_string()) } -fn bad_request(message: impl fmt::Display) -> Response { - error(Status::BAD_REQUEST, message) +fn bad_request(message: impl fmt::Display) -> HttpError { + (Status::BAD_REQUEST, message.to_string()) } -fn unsupported_media_type(content_type: &str) -> Response { - error( +fn unsupported_media_type(content_type: &str) -> HttpError { + ( Status::UNSUPPORTED_MEDIA_TYPE, format!("No supported content Content-Type given: {}", content_type), ) } -fn internal_server_error(message: impl fmt::Display) -> Response { +fn internal_server_error(message: impl fmt::Display) -> HttpError { eprintln!("Internal server error: {}", message); - error(Status::INTERNAL_SERVER_ERROR, message) + (Status::INTERNAL_SERVER_ERROR, message.to_string()) } /// Hacky tool to allow implementing read on top of a write loop @@ -1071,22 +976,21 @@ impl io::Result>) + 'static> ReadForWrite io::Result, add_more_data: U, content_type: &'static str, - ) -> Response { + ) -> Result { let buffer = Rc::new(RefCell::new(Vec::new())); - match initial_state_builder(ReadForWriteWriter { + let state = initial_state_builder(ReadForWriteWriter { buffer: buffer.clone(), - }) { - Ok(state) => Response::builder(Status::OK) - .with_header(HeaderName::CONTENT_TYPE, content_type) - .unwrap() - .with_body(Body::from_read(Self { - buffer, - position: 0, - add_more_data, - state: Some(state), - })), - Err(e) => internal_server_error(e), - } + }) + .map_err(internal_server_error)?; + Ok(Response::builder(Status::OK) + .with_header(HeaderName::CONTENT_TYPE, content_type) + .unwrap() + .with_body(Body::from_read(Self { + buffer, + position: 0, + add_more_data, + state: Some(state), + }))) } } @@ -1798,6 +1702,7 @@ mod tests { fn exec(&self, mut request: Request) -> Response { handle_request(&mut request, self.store.clone()) + .unwrap_or_else(|(status, message)| error(status, message)) } fn test_status(&self, request: Request, expected_status: Status) {