From 8155af5f0a8249deac8b45a4943addbd9e47440c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ant=C3=B4nio=20Cardoso?= Date: Tue, 10 Dec 2024 13:42:11 -0300 Subject: [PATCH 1/3] cargo: add thiserror crate --- Cargo.lock | 65 ++++++++++++++++++++++++++++++++++++------------------ Cargo.toml | 1 + 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac6c3211..817a6b28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,7 @@ dependencies = [ "dashmap", "futures", "log", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -280,7 +280,7 @@ dependencies = [ "serde_json", "serde_qs", "serde_urlencoded", - "thiserror", + "thiserror 1.0.61", "validator", ] @@ -832,7 +832,7 @@ dependencies = [ "once_cell", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", "tokio", ] @@ -1755,7 +1755,7 @@ dependencies = [ "libc", "memchr", "smallvec", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -1887,7 +1887,7 @@ dependencies = [ "paste", "pin-project-lite", "smallvec", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -1912,7 +1912,7 @@ dependencies = [ "paste", "pin-project-lite", "smallvec", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -2117,7 +2117,7 @@ dependencies = [ "gstreamer-video-sys", "libc", "once_cell", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -2793,7 +2793,7 @@ dependencies = [ "proc-macro2", "quick-xml 0.26.0", "quote", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -2846,6 +2846,7 @@ dependencies = [ "shellexpand", "sysinfo", "thirtyfour", + "thiserror 2.0.6", "tokio", "tracing", "tracing-actix-web", @@ -3140,7 +3141,7 @@ dependencies = [ "reqwest", "schema", "sha1 0.6.1", - "thiserror", + "thiserror 1.0.61", "tokio", "tokio-stream", "tracing", @@ -3254,7 +3255,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_yaml", - "thiserror", + "thiserror 1.0.61", "url", ] @@ -3290,7 +3291,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "thiserror", + "thiserror 1.0.61", "url", "uuid 0.8.2", "uuid 1.8.0", @@ -3729,7 +3730,7 @@ checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -4123,7 +4124,7 @@ dependencies = [ "futures", "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -4446,7 +4447,7 @@ dependencies = [ "rand", "ring 0.17.8", "subtle", - "thiserror", + "thiserror 1.0.61", "tokio", "url", "webrtc-util", @@ -4617,7 +4618,7 @@ dependencies = [ "stringmatch", "strum 0.26.2", "thirtyfour-macros", - "thiserror", + "thiserror 1.0.61", "tokio", "tracing", "url", @@ -4641,7 +4642,16 @@ version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.61", +] + +[[package]] +name = "thiserror" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +dependencies = [ + "thiserror-impl 2.0.6", ] [[package]] @@ -4655,6 +4665,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "thiserror-impl" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -5053,7 +5074,7 @@ version = "0.1.0" source = "git+https://github.com/lumeohq/onvif-rs?rev=8e2408db#8e2408dbbf59dfed2e58b30cfe3a8ae09affaa9f" dependencies = [ "async-trait", - "thiserror", + "thiserror 1.0.61", "yaserde", ] @@ -5069,7 +5090,7 @@ version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d3fa4606cdab1e9b668cc65ce2545941d01f52bc27536a195c66c55b91cb84" dependencies = [ - "thiserror", + "thiserror 1.0.61", "ts-rs-macros", "uuid 1.8.0", ] @@ -5101,7 +5122,7 @@ dependencies = [ "log", "rand", "sha1 0.10.6", - "thiserror", + "thiserror 1.0.61", "url", "utf-8", ] @@ -5120,7 +5141,7 @@ dependencies = [ "rand", "ring 0.17.8", "stun", - "thiserror", + "thiserror 1.0.61", "tokio", "tokio-util", "webrtc-util", @@ -5345,7 +5366,7 @@ dependencies = [ "getset", "git2", "rustversion", - "thiserror", + "thiserror 1.0.61", "time", ] @@ -5510,7 +5531,7 @@ dependencies = [ "log", "nix", "rand", - "thiserror", + "thiserror 1.0.61", "tokio", "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index 2996113b..e4824c1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ actix-cors = "0.7.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" validator = { version = "0.16", features = ["derive"] } +thiserror = "2.0" ## FINAL sysinfo = "0.29" From 692425a8a4ac0e302ac3b9622005091479f9822e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ant=C3=B4nio=20Cardoso?= Date: Tue, 10 Dec 2024 13:42:57 -0300 Subject: [PATCH 2/3] src: lib: server: Specify the server errors API --- src/lib/server/error.rs | 55 +++++++++++++++++++++++++++++++++++++++++ src/lib/server/mod.rs | 1 + 2 files changed, 56 insertions(+) create mode 100644 src/lib/server/error.rs diff --git a/src/lib/server/error.rs b/src/lib/server/error.rs new file mode 100644 index 00000000..b8b08d16 --- /dev/null +++ b/src/lib/server/error.rs @@ -0,0 +1,55 @@ +use actix_web::{http::StatusCode, ResponseError}; + +use paperclip::actix::api_v2_errors; +use validator::ValidationErrors; + +pub type Result = actix_web::Result; + +#[allow(dead_code)] +#[api_v2_errors( + code = 400, + description = "Bad Request: The client's request contains invalid or malformed data.", + code = 404, + description = "Not Found: The requested path or entity does not exist.", + code = 500, + description = "Internal Server Error: An unexpected server error has occurred.", + code = 503, + description = "Service Unavailable: ." +)] +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Bad Request: {0}")] + BadRequest(String), + + #[error("Not Found: {0}")] + NotFound(String), + + #[error("Internal Server Error: {0}")] + Internal(String), + + #[error("Service Unavailable: {0}")] + Unavailable(String), +} + +impl ResponseError for Error { + fn status_code(&self) -> StatusCode { + match self { + Self::BadRequest(_) => StatusCode::BAD_REQUEST, + Self::NotFound(_) => StatusCode::NOT_FOUND, + Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::Unavailable(_) => StatusCode::SERVICE_UNAVAILABLE, + } + } +} + +impl From for Error { + fn from(error: ValidationErrors) -> Self { + Self::BadRequest(error.to_string()) + } +} + +impl From for Error { + fn from(error: actix_web_validator::Error) -> Self { + Self::BadRequest(error.to_string()) + } +} diff --git a/src/lib/server/mod.rs b/src/lib/server/mod.rs index 272f4a52..7241b7ad 100644 --- a/src/lib/server/mod.rs +++ b/src/lib/server/mod.rs @@ -1,2 +1,3 @@ +mod error; pub mod manager; mod pages; From 25b6f6ff240cd70cb4759f6499727796a8527eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ant=C3=B4nio=20Cardoso?= Date: Tue, 10 Dec 2024 14:42:19 -0300 Subject: [PATCH 3/3] src: server: pages: Refactor to use the API errors --- src/lib/server/pages.rs | 314 +++++++++++++++++----------------------- 1 file changed, 134 insertions(+), 180 deletions(-) diff --git a/src/lib/server/pages.rs b/src/lib/server/pages.rs index 3ee5cd95..f3397bbb 100644 --- a/src/lib/server/pages.rs +++ b/src/lib/server/pages.rs @@ -4,7 +4,7 @@ use actix_web::{ http::header, rt, web::{self, Json}, - Error, HttpRequest, HttpResponse, + HttpRequest, HttpResponse, }; use paperclip::actix::{api_v2_operation, Apiv2Schema, CreatedJson}; use serde::{Deserialize, Serialize}; @@ -13,7 +13,9 @@ use validator::Validate; use crate::{ controls::types::Control, - helper, settings, + helper, + server::error::{Error, Result}, + settings, stream::{gst as gst_stream, manager as stream_manager, types::StreamInformation}, video::{ types::{Format, VideoSourceType}, @@ -167,7 +169,7 @@ fn load_webrtc(filename: &str) -> String { } #[api_v2_operation] -pub fn root(req: HttpRequest) -> HttpResponse { +pub fn root(req: HttpRequest) -> Result { let filename = match req.match_info().query("filename") { "" | "index.html" => "index.html", "vue.js" => "vue.js", @@ -176,9 +178,9 @@ pub fn root(req: HttpRequest) -> HttpResponse { something => { //TODO: do that in load_file - return HttpResponse::NotFound() - .content_type("text/plain") - .body(format!("Page does not exist: {something:?}")); + return Err(Error::NotFound(format!( + "Page does not exist: {something:?}" + ))); } }; let content = load_file(filename); @@ -188,20 +190,20 @@ pub fn root(req: HttpRequest) -> HttpResponse { .unwrap_or(""); let mime = actix_files::file_extension_to_mime(extension).to_string(); - HttpResponse::Ok().content_type(mime).body(content) + Ok(HttpResponse::Ok().content_type(mime).body(content)) } #[api_v2_operation] /// Provide information about the running service /// There is no stable API guarantee for the development field -pub async fn info() -> CreatedJson { - CreatedJson(Info::new()) +pub async fn info() -> Result> { + Ok(CreatedJson(Info::new())) } //TODO: change endpoint name to sources #[api_v2_operation] /// Provides list of all video sources, with controls and formats -pub async fn v4l() -> Json> { +pub async fn v4l() -> Result>> { let cameras = video_source::cameras_available().await; use futures::stream::{self, StreamExt}; @@ -238,160 +240,123 @@ pub async fn v4l() -> Json> { .collect() .await; - Json(cameras) + Ok(Json(cameras)) } #[api_v2_operation] /// Change video control for a specific source -pub async fn v4l_post(json: web::Json) -> HttpResponse { +pub async fn v4l_post(json: web::Json) -> Result { let control = json.into_inner(); - let answer = video_source::set_control(&control.device, control.v4l_id, control.value).await; + video_source::set_control(&control.device, control.v4l_id, control.value) + .await + .map_err(|error| Error::Internal(format!("{error:?}")))?; - if let Err(error) = answer { - return HttpResponse::NotAcceptable() - .content_type("text/plain") - .body(format!("{error:#?}")); - } - - HttpResponse::Ok().finish() + Ok(HttpResponse::Ok().finish()) } #[api_v2_operation] /// Reset service settings -pub async fn reset_settings(query: web::Query) -> HttpResponse { +pub async fn reset_settings(query: web::Query) -> Result { if query.all.unwrap_or_default() { settings::manager::reset().await; - if let Err(error) = stream_manager::start_default().await { - return HttpResponse::InternalServerError() - .content_type("text/plain") - .body(format!("{error:#?}")); - }; - return HttpResponse::Ok().finish(); + stream_manager::start_default() + .await + .map_err(|error| Error::Internal(format!("{error:?}")))?; + + return Ok(HttpResponse::Ok().finish()); } - HttpResponse::NotAcceptable() - .content_type("text/plain") - .body("Missing argument for reset_settings.") + Err(Error::Internal( + "Missing argument for reset_settings.".to_string(), + )) } #[api_v2_operation] /// Provide a list of all streams configured -pub async fn streams() -> HttpResponse { - let streams = match stream_manager::streams().await { - Ok(streams) => streams, - Err(error) => { - return HttpResponse::InternalServerError() - .content_type("text/plain") - .body(format!("{error:#?}")) - } - }; +pub async fn streams() -> Result { + let streams = stream_manager::streams() + .await + .map_err(|error| Error::Internal(format!("{error:?}")))?; - match serde_json::to_string_pretty(&streams) { - Ok(json) => HttpResponse::Ok() - .content_type("application/json") - .body(json), - Err(error) => HttpResponse::InternalServerError() - .content_type("text/plain") - .body(format!("{error:#?}")), - } + let json = serde_json::to_string_pretty(&streams) + .map_err(|error| Error::Internal(format!("{error:?}")))?; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(json)) } #[api_v2_operation] /// Create a video stream -pub async fn streams_post(json: web::Json) -> HttpResponse { +pub async fn streams_post(json: web::Json) -> Result { let json = json.into_inner(); - let video_source = match video_source::get_video_source(&json.source).await { - Ok(video_source) => video_source, - Err(error) => { - return HttpResponse::NotAcceptable() - .content_type("text/plain") - .body(format!("{error:#?}")); - } - }; + let video_source = video_source::get_video_source(&json.source) + .await + .map_err(|error| Error::Internal(format!("{error:?}")))?; - if let Err(error) = stream_manager::add_stream_and_start(VideoAndStreamInformation { + stream_manager::add_stream_and_start(VideoAndStreamInformation { name: json.name, stream_information: json.stream_information, video_source, }) .await - { - return HttpResponse::NotAcceptable() - .content_type("text/plain") - .body(format!("{error:#?}")); - } + .map_err(|error| Error::Internal(format!("{error:?}")))?; // Return the new streams available - streams().await + streams() + .await + .map_err(|error| Error::Internal(format!("{error:?}"))) } #[api_v2_operation] /// Remove a desired stream -pub fn remove_stream(query: web::Query) -> HttpResponse { - if let Err(error) = stream_manager::remove_stream_by_name(&query.name).await { - return HttpResponse::NotAcceptable() - .content_type("text/plain") - .body(format!("{error:#?}")); - } +pub fn remove_stream(query: web::Query) -> Result { + stream_manager::remove_stream_by_name(&query.name) + .await + .map_err(|error| Error::Internal(format!("{error:?}")))?; - let streams = match stream_manager::streams().await { - Ok(streams) => streams, - Err(error) => { - return HttpResponse::InternalServerError() - .content_type("text/plain") - .body(format!("{error:#?}")) - } - }; + let streams = stream_manager::streams() + .await + .map_err(|error| Error::Internal(format!("{error:?}")))?; - match serde_json::to_string_pretty(&streams) { - Ok(json) => HttpResponse::Ok() - .content_type("application/json") - .body(json), - Err(error) => HttpResponse::InternalServerError() - .content_type("text/plain") - .body(format!("{error:#?}")), - } + let json = serde_json::to_string_pretty(&streams) + .map_err(|error| Error::Internal(format!("{error:?}")))?; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(json)) } #[api_v2_operation] /// Reset controls from a given camera source -pub fn camera_reset_controls(json: web::Json) -> HttpResponse { +pub fn camera_reset_controls(json: web::Json) -> Result { if let Err(errors) = video_source::reset_controls(&json.device).await { let mut error: String = Default::default(); errors .iter() .enumerate() .for_each(|(i, e)| error.push_str(&format!("{}: {e}\n", i + 1))); - return HttpResponse::NotAcceptable() - .content_type("text/plain") - .body(format!( - "One or more controls were not reseted due to the following errors: \n{error:#?}", - )); + return Err(Error::Internal(format!( + "One or more controls were not reseted due to the following errors: \n{error:#?}", + ))); } - let streams = match stream_manager::streams().await { - Ok(streams) => streams, - Err(error) => { - return HttpResponse::InternalServerError() - .content_type("text/plain") - .body(format!("{error:#?}")) - } - }; + let streams = stream_manager::streams() + .await + .map_err(|error| Error::Internal(format!("{error:?}")))?; - match serde_json::to_string_pretty(&streams) { - Ok(json) => HttpResponse::Ok() - .content_type("application/json") - .body(json), - Err(error) => HttpResponse::InternalServerError() - .content_type("text/plain") - .body(format!("{error:#?}")), - } + let json = serde_json::to_string_pretty(&streams) + .map_err(|error| Error::Internal(format!("{error:?}")))?; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(json)) } #[api_v2_operation] /// Provides a xml description file that contains information for a specific device, based on: https://mavlink.io/en/services/camera_def.html -pub fn xml(xml_file_request: web::Query) -> HttpResponse { +pub fn xml(xml_file_request: web::Query) -> Result { debug!("{xml_file_request:#?}"); let cameras = video_source::cameras_available().await; let camera = cameras @@ -399,50 +364,48 @@ pub fn xml(xml_file_request: web::Query) -> HttpResponse { .find(|source| source.inner().source_string() == xml_file_request.file); let Some(camera) = camera else { - return HttpResponse::NotFound() - .content_type("text/plain") - .body(format!( - "File for {} does not exist.", - xml_file_request.file - )); + return Err(Error::NotFound(format!( + "File for {} does not exist.", + xml_file_request.file + ))); }; - match xml::from_video_source(camera.inner()) { - Ok(xml) => HttpResponse::Ok().content_type("text/xml").body(xml), - Err(error) => HttpResponse::InternalServerError().body(format!( + let xml = xml::from_video_source(camera.inner()).map_err(|error| { + Error::Internal(format!( "Failed getting XML file {}: {error:?}", xml_file_request.file - )), - } + )) + })?; + + Ok(HttpResponse::Ok().content_type("text/xml").body(xml)) } #[api_v2_operation] /// Provides a sdp description file that contains information for a specific stream, based on: [RFC 8866](https://www.rfc-editor.org/rfc/rfc8866.html) -pub fn sdp(sdp_file_request: web::Query) -> HttpResponse { +pub fn sdp(sdp_file_request: web::Query) -> Result { debug!("{sdp_file_request:#?}"); - match stream_manager::get_first_sdp_from_source(sdp_file_request.source.clone()).await { - Ok(sdp) => { - if let Ok(sdp) = sdp.as_text() { - HttpResponse::Ok().content_type("text/plain").body(sdp) - } else { - HttpResponse::InternalServerError() - .content_type("text/plain") - .body("Failed to convert SDP to text".to_string()) - } - } - Err(error) => HttpResponse::NotFound() - .content_type("text/plain") - .body(format!( + let sdp = stream_manager::get_first_sdp_from_source(sdp_file_request.source.clone()) + .await + .map_err(|error| { + Error::Internal(format!( "Failed to get SDP file for {:?}. Reason: {error:?}", sdp_file_request.source - )), - } + )) + })?; + + let sdp_text = sdp + .as_text() + .map_err(|error| Error::Internal(format!("Failed to convert SDP to text: {error:?}")))?; + + Ok(HttpResponse::Ok().content_type("text/plain").body(sdp_text)) } #[api_v2_operation] /// Provides a thumbnail file of the given source -pub async fn thumbnail(thumbnail_file_request: web::Query) -> HttpResponse { +pub async fn thumbnail( + thumbnail_file_request: web::Query, +) -> Result { // Ideally, we should be using `actix_web_validator::Query` instead of `web::Query`, // but because paperclip (at least until 0.8) is using `actix-web-validator 3.x`, // and `validator 0.14`, the newest api needed to use it along #[api_v2_operation] @@ -453,7 +416,9 @@ pub async fn thumbnail(thumbnail_file_request: web::Query) // rid of this workaround. if let Err(errors) = thumbnail_file_request.validate() { warn!("Failed validating ThumbnailFileRequest. Reason: {errors:?}"); - return actix_web::ResponseError::error_response(&actix_web_validator::Error::from(errors)); + return Ok(actix_web::ResponseError::error_response( + &actix_web_validator::Error::from(errors), + )); } let source = thumbnail_file_request.source.clone(); @@ -461,43 +426,43 @@ pub async fn thumbnail(thumbnail_file_request: web::Query) let target_height = thumbnail_file_request.target_height.map(|v| v as u32); match stream_manager::get_jpeg_thumbnail_from_source(source, quality, target_height).await { - Some(Ok(image)) => HttpResponse::Ok().content_type("image/jpeg").body(image), - None => HttpResponse::NotFound() + Some(Ok(image)) => Ok(HttpResponse::Ok().content_type("image/jpeg").body(image)), + None => Ok(HttpResponse::NotFound() .content_type("text/plain") .body(format!( "Thumbnail not found for source {:?}.", thumbnail_file_request.source - )), - Some(Err(error)) => HttpResponse::ServiceUnavailable() + ))), + Some(Err(error)) => Ok(HttpResponse::ServiceUnavailable() .reason("Thumbnail temporarily unavailable") .insert_header((header::RETRY_AFTER, 10)) .content_type("text/plain") .body(format!( "Thumbnail for source {:?} is temporarily unavailable. Try again later. Details: {error:?}", thumbnail_file_request.source - )), + ))), } } #[api_v2_operation] /// Provides information related to all gst plugins available for camera manager -pub async fn gst_info() -> HttpResponse { +pub async fn gst_info() -> Result { let gst_info = gst_stream::info::Info::default(); - match serde_json::to_string_pretty(&gst_info) { - Ok(json) => HttpResponse::Ok() - .content_type("application/json") - .body(json), - Err(error) => HttpResponse::InternalServerError() - .content_type("text/plain") - .body(format!("{error:#?}")), - } + let json = serde_json::to_string_pretty(&gst_info) + .map_err(|error| Error::Internal(format!("{error:?}")))?; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(json)) } #[api_v2_operation] /// Provides a access point for the service log -pub async fn log(req: HttpRequest, stream: web::Payload) -> Result { - let (response, mut session, _stream) = actix_ws::handle(&req, stream)?; +pub async fn log(req: HttpRequest, stream: web::Payload) -> Result { + let (response, mut session, _stream) = + actix_ws::handle(&req, stream).map_err(|error| Error::Internal(format!("{error:?}")))?; + rt::spawn(async move { let (mut receiver, history) = crate::logger::manager::HISTORY.lock().unwrap().subscribe(); @@ -518,24 +483,22 @@ pub async fn log(req: HttpRequest, stream: web::Payload) -> Result HttpResponse { +pub async fn onvif_devices() -> Result { let onvif_devices = crate::controls::onvif::manager::Manager::onvif_devices().await; - match serde_json::to_string_pretty(&onvif_devices) { - Ok(json) => HttpResponse::Ok() - .content_type("application/json") - .body(json), - Err(error) => HttpResponse::InternalServerError() - .content_type("text/plain") - .body(format!("{error:#?}")), - } + let json = serde_json::to_string_pretty(&onvif_devices) + .map_err(|error| Error::Internal(format!("{error:?}")))?; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(json)) } #[api_v2_operation] pub async fn authenticate_onvif_device( query: web::Query, -) -> HttpResponse { - if let Err(error) = crate::controls::onvif::manager::Manager::register_credentials( +) -> Result { + crate::controls::onvif::manager::Manager::register_credentials( query.device_uuid, Some(onvif::soap::client::Credentials { username: query.username.clone(), @@ -543,27 +506,18 @@ pub async fn authenticate_onvif_device( }), ) .await - { - return HttpResponse::InternalServerError() - .content_type("text/plain") - .body(format!("{error:#?}")); - } + .map_err(|error| Error::Internal(format!("{error:?}")))?; - HttpResponse::Ok().finish() + Ok(HttpResponse::Ok().finish()) } #[api_v2_operation] pub async fn unauthenticate_onvif_device( query: web::Query, -) -> HttpResponse { - if let Err(error) = - crate::controls::onvif::manager::Manager::register_credentials(query.device_uuid, None) - .await - { - return HttpResponse::InternalServerError() - .content_type("text/plain") - .body(format!("{error:#?}")); - } +) -> Result { + crate::controls::onvif::manager::Manager::register_credentials(query.device_uuid, None) + .await + .map_err(|error| Error::Internal(format!("{error:?}")))?; - HttpResponse::Ok().finish() + Ok(HttpResponse::Ok().finish()) }