Skip to content

Commit 31f95ca

Browse files
committed
src: server: Add restAPI and websocket services
1 parent 7b09a7b commit 31f95ca

8 files changed

Lines changed: 381 additions & 4 deletions

File tree

src/server/manager.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
1-
use actix_web::{middleware, App, HttpServer};
2-
use paperclip::actix::OpenApiExt;
1+
use crate::device::manager::ManagerActorHandler;
2+
3+
use super::protocols;
4+
use actix_web::{middleware, web::Data, App, HttpServer};
35
use tracing::info;
46

5-
pub async fn run(server_address: &str) -> std::io::Result<()> {
7+
use paperclip::actix::{
8+
web::{self, Scope},
9+
OpenApiExt,
10+
};
11+
12+
fn add_v1_paths(scope: Scope) -> Scope {
13+
scope
14+
.configure(protocols::v1::rest::register_services)
15+
.service(protocols::v1::websocket::websocket)
16+
}
17+
18+
pub async fn run(server_address: &str, handler: ManagerActorHandler) -> std::io::Result<()> {
619
let server_address = server_address.to_string();
720
info!("starting HTTP server at http://{server_address}");
821

9-
let server = HttpServer::new(|| {
22+
let server = HttpServer::new(move || {
23+
let v1 = add_v1_paths(web::scope("/v1"));
24+
let default = add_v1_paths(web::scope(""));
25+
1026
App::new()
27+
.app_data(Data::new(handler.clone()))
1128
.wrap(middleware::Logger::default())
1229
.wrap_api()
1330
.with_json_spec_at("/api/spec")
1431
.with_swagger_ui_at("/docs")
32+
.service(v1)
33+
.service(default)
1534
.build()
1635
});
1736

src/server/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod manager;
2+
pub mod protocols;

src/server/protocols/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod v1;

src/server/protocols/v1/errors.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use actix_web::{http::StatusCode, ResponseError};
2+
3+
use paperclip::actix::api_v2_errors;
4+
use validator::ValidationErrors;
5+
6+
#[allow(dead_code)]
7+
#[api_v2_errors(
8+
code = 400,
9+
description = "Bad Request: The client's request contains invalid or malformed data.",
10+
code = 500,
11+
description = "Internal Server Error: An unexpected server error has occurred."
12+
)]
13+
#[derive(Debug, thiserror::Error)]
14+
pub enum Error {
15+
#[error("Bad Request: {0}")]
16+
BadRequest(String),
17+
#[error("Internal Server Error: {0}")]
18+
Internal(String),
19+
}
20+
21+
impl ResponseError for Error {
22+
fn status_code(&self) -> StatusCode {
23+
match self {
24+
Self::BadRequest(_) => StatusCode::BAD_REQUEST,
25+
Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
26+
}
27+
}
28+
}
29+
30+
impl From<ValidationErrors> for Error {
31+
fn from(error: ValidationErrors) -> Self {
32+
Self::BadRequest(error.to_string())
33+
}
34+
}
35+
36+
impl From<crate::device::manager::ManagerError> for Error {
37+
fn from(error: crate::device::manager::ManagerError) -> Self {
38+
Self::Internal(serde_json::to_string_pretty(&error).unwrap_or_default())
39+
}
40+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Ping Viewer Next</title>
7+
<script>
8+
function redirectToDocs() {
9+
window.location.href = '/docs/';
10+
}
11+
</script>
12+
</head>
13+
<body>
14+
<h1>Ping Viewer Next</h1>
15+
<button onclick="redirectToDocs()">Check API specifications</button>
16+
</body>
17+
</html>

src/server/protocols/v1/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod errors;
2+
pub mod rest;
3+
pub mod websocket;

src/server/protocols/v1/rest.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use crate::device::manager::ManagerActorHandler;
2+
use crate::server::protocols::v1::errors::Error;
3+
use actix_web::Responder;
4+
use mime_guess::from_path;
5+
use paperclip::actix::{
6+
api_v2_operation, get, post,
7+
web::{self, HttpResponse, Json},
8+
Apiv2Schema,
9+
};
10+
use serde::{Deserialize, Serialize};
11+
use serde_json::json;
12+
13+
#[derive(rust_embed::RustEmbed)]
14+
#[folder = "src/server/protocols/v1/frontend"]
15+
struct Asset;
16+
17+
fn handle_embedded_file(path: &str) -> HttpResponse {
18+
match Asset::get(path) {
19+
Some(content) => HttpResponse::Ok()
20+
.content_type(from_path(path).first_or_octet_stream().as_ref())
21+
.body(content.data.into_owned()),
22+
None => HttpResponse::NotFound().body("404 Not Found"),
23+
}
24+
}
25+
26+
#[api_v2_operation(skip)]
27+
#[get("/")]
28+
async fn index() -> impl Responder {
29+
handle_embedded_file("index.html")
30+
}
31+
32+
/// The "register_service" route is used by BlueOS extensions manager
33+
#[api_v2_operation]
34+
#[get("register_service")]
35+
async fn server_metadata() -> Result<Json<ServerMetadata>, Error> {
36+
let package = ServerMetadata::default();
37+
Ok(Json(package))
38+
}
39+
40+
pub fn register_services(cfg: &mut web::ServiceConfig) {
41+
cfg.service(index)
42+
.service(post_request)
43+
.service(server_metadata);
44+
}
45+
46+
#[api_v2_operation]
47+
#[post("device/request")]
48+
async fn post_request(
49+
manager_handler: web::Data<ManagerActorHandler>,
50+
json: web::Json<crate::device::manager::Request>,
51+
) -> Result<Json<crate::device::manager::Answer>, Error> {
52+
let request = json.into_inner();
53+
54+
let answer = manager_handler.send(request).await?;
55+
56+
crate::server::protocols::v1::websocket::send_to_websockets(json!(answer), None);
57+
58+
Ok(Json(answer))
59+
}
60+
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
61+
pub struct ServerMetadata {
62+
pub name: &'static str,
63+
pub description: &'static str,
64+
pub icon: &'static str,
65+
pub company: &'static str,
66+
pub version: &'static str,
67+
pub new_page: bool,
68+
pub webpage: &'static str,
69+
pub api: &'static str,
70+
}
71+
72+
impl Default for ServerMetadata {
73+
fn default() -> Self {
74+
Self {
75+
name: "Ping Viewer Next",
76+
description: "A ping protocol extension for expose devices to web.",
77+
icon: "mdi-compass-outline",
78+
company: "BlueRobotics",
79+
version: "0.0.1",
80+
new_page: false,
81+
webpage: "https://github.com/RaulTrombin/navigator-assistant",
82+
api: "/docs",
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)