urdf-viz 0.12.0

URDF visualization
use rouille;
use rouille::Response;
use std::sync::{Arc, Mutex};

#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct JointNamesAndPositions {
    pub names: Vec<String>,
    pub positions: Vec<f32>,
}

#[derive(Debug, Clone)]
pub struct JointNamesAndPositionsRequest {
    pub joint_positions: JointNamesAndPositions,
    pub requested: bool,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct ResultResponse {
    is_ok: bool,
    reason: String,
}

#[derive(Debug)]
pub struct WebServer {
    pub port: u16,
    pub target_joint_positions: Arc<Mutex<JointNamesAndPositionsRequest>>,
    pub current_joint_positions: Arc<Mutex<JointNamesAndPositions>>,
}

impl WebServer {
    pub fn new(port: u16) -> Self {
        Self {
            port,
            target_joint_positions: Arc::new(Mutex::new(JointNamesAndPositionsRequest {
                joint_positions: JointNamesAndPositions::default(),
                requested: false,
            })),
            current_joint_positions: Arc::new(Mutex::new(JointNamesAndPositions::default())),
        }
    }

    pub fn clone_in_out(
        &self,
    ) -> (
        Arc<Mutex<JointNamesAndPositionsRequest>>,
        Arc<Mutex<JointNamesAndPositions>>,
    ) {
        (
            self.target_joint_positions.clone(),
            self.current_joint_positions.clone(),
        )
    }
    pub fn start(self) {
        rouille::start_server(("0.0.0.0", self.port), move |request| {
            router!(request,
                    (POST) (/set_joint_positions) => {
                        let json: JointNamesAndPositions = try_or_400!(rouille::input::json_input(request));
                        let mut ja = try_or_404!(self.target_joint_positions.lock());
                        ja.joint_positions = json;
                        if ja.joint_positions.names.len() != ja.joint_positions.positions.len() {
                            Response::json(&ResultResponse { is_ok: false,
                                                     reason: format!("names and positions size mismatch ({} != {})",
                                                        ja.joint_positions.names.len(), ja.joint_positions.positions.len()) })
                        } else {
                            ja.requested = true;
                            Response::json(&ResultResponse { is_ok: true, reason: "".to_string() })
                        }
                    },
                    (OPTIONS) (/set_joint_angles) => {
                        Response::empty_204()
                            .with_additional_header("Allow", "OPTIONS, POST")
                            .with_additional_header("Access-Control-Allow-Methods", "POST")
                            .with_additional_header("Access-Control-Allow-Origin", "*")
                            .with_additional_header("Access-Control-Allow-Headers", "authorization,content-type")
                            .with_additional_header("Access-Control-Max-Age", "86400")
                    },
                    (GET) (/get_joint_angles) => {
                        let ja = try_or_404!(self.current_joint_positions.lock());
                        Response::json(&*ja)
                    },
                    (OPTIONS) (/get_joint_angles) => {
                        Response::empty_204()
                            .with_additional_header("Allow", "OPTIONS, GET")
                            .with_additional_header("Access-Control-Allow-Methods", "GET")
                            .with_additional_header("Access-Control-Allow-Origin", "*")
                            .with_additional_header("Access-Control-Allow-Headers", "authorization")
                            .with_additional_header("Access-Control-Max-Age", "86400")
                    },

                    _ => Response::empty_404(),
                )
        });
    }
}