Skip to content

Commit

Permalink
Propose a much larger solution including new spec update (#24)
Browse files Browse the repository at this point in the history
* try things about option check and how to manage error in chain

* upgrade thinking

* learn about combinatoire

* update and validate with tests

* use option instead

* try implement

* better

* just have to integrate it into serivdes

* up

* continue integration

* service functions need to return err into in order to be able to stop correclty

* Update boot service to stop platform when file not present
  • Loading branch information
XdoctorwhoZ committed May 7, 2024
1 parent d98fd35 commit afd318e
Show file tree
Hide file tree
Showing 6 changed files with 455 additions and 91 deletions.
224 changes: 224 additions & 0 deletions src/platform/connection_info/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
use std::env;
use std::io::Write;
use std::path::PathBuf;

use serde_json::json;
use serde_json::Map as JsonMap;
use serde_json::Value as JsonValue;
use std::fs::File;

mod tests;


#[derive(Debug)]
pub enum CiErrorType {
// COVER:REQ_CONN_INFO_0030_00
ContentBadFormat,
// COVER:REQ_CONN_INFO_0040_00
MandatoryFieldMissing,
// COVER:REQ_CONN_INFO_0050_00
FileDoesNotExist,
}


#[derive(Debug)]
pub struct CiError {

type_: CiErrorType,

message: String,
}

impl CiError {

fn new(type_: CiErrorType, message: &str) -> Self {
Self {
type_,
message: message.to_string(),
}
}

pub fn message(&self) -> &str {
&self.message
}

pub fn type_(&self) -> &CiErrorType {
&self.type_
}
}

fn content_bad_format_error(message: &str) -> CiError {
CiError::new(CiErrorType::ContentBadFormat, message)
}
fn mandatory_field_missing_error(message: &str) -> CiError {
CiError::new(CiErrorType::MandatoryFieldMissing, message)
}
fn file_does_not_exist_error(message: &str) -> CiError {
CiError::new(CiErrorType::FileDoesNotExist, message)
}


/// This object is responsible of the connection information
///
/// It must manage the data but also the file used to store them
///
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ConnectionInfo {
// Path of the file
file_path: String,

// broker info
host_addr: String,
host_port: u16,
host_retry: u32,

// credential

}

impl ConnectionInfo {

/// Create a new ConnectionInfo object with default values
///
pub fn default() -> Self {
Self {
file_path: ConnectionInfo::system_file_path().to_str().unwrap().to_string(),
host_addr: "localhost".to_string(),
host_port: 1883,
host_retry: 1,
}
}

/// Give the system path of the connection.json file
///
/// COVER:REQ_CONN_INFO_0010_00
///
pub fn system_file_path() -> PathBuf {
// Define the paths
let filename = "connection.json";
let unix_path =
PathBuf::from("/etc/panduza").join(filename);
let windows_path =
PathBuf::from(dirs::home_dir().unwrap()).join("panduza").join(filename);

// Return the file path depeding on the OS
match env::consts::OS {
"windows" => {
return windows_path;
}
"unix" => {
return unix_path;
}
_ => {
tracing::warn!("Unsupported system bu try with unix path anyway !");
return unix_path;
}
}
}

/// Create a new ConnectionInfo object from a JSON file
///
pub async fn build_from_file() -> Result<Self, CiError> {
// Get the file path
let file_path = ConnectionInfo::system_file_path();

// Check if the file exists
if !file_path.exists() {
return Err(file_does_not_exist_error(file_path.to_str().unwrap()));
}

// Try to read the file content
tokio::fs::read_to_string(&file_path).await
.map_err(|e| content_bad_format_error(e.to_string().as_str()))
.and_then(|v| ConnectionInfo::build_from_str(v.as_str()) )
}

/// Create a new ConnectionInfo object from a JSON string
///
fn build_from_str(json_string: &str) -> Result<Self, CiError> {
serde_json::from_str(json_string)
.map_err(|e| content_bad_format_error(e.to_string().as_str()))
.and_then(ConnectionInfo::build_from_json_value)
}

/// Create a new ConnectionInfo object from a JSON value
///
fn build_from_json_value(json_obj: JsonValue) -> Result<Self, CiError> {
json_obj.as_object()
.ok_or(content_bad_format_error( "Except a JSON object at file root"))
.and_then(ConnectionInfo::build_from_map_object)
}

/// Create a new ConnectionInfo object from a JSON map object
///
fn build_from_map_object(map_obj: &JsonMap<String, JsonValue>) -> Result<Self, CiError> {

// Get Host Section
let host = map_obj.get("host")
.ok_or(mandatory_field_missing_error("[host] section must be provided"))?;

// Get Host Address
let host_addr = host.get("addr")
.ok_or(mandatory_field_missing_error("[host.addr] must be provided"))?
.as_str()
.ok_or(content_bad_format_error("[host.addr] must be a string"))?
.to_string();

// Get Host Port
let host_port = host.get("port")
.ok_or(mandatory_field_missing_error("[host.port] must be provided"))?
.as_u64()
.ok_or(content_bad_format_error("[host.port] must be a number"))?
as u16;

// Get Host Retry
let default_retry_value: u32 = 1;
let host_retry = host.get("retry")
.unwrap_or(&json!(default_retry_value))
.as_u64()
.ok_or(content_bad_format_error("[host.retry] must be a number"))?
as u32;

Ok(
Self {
file_path: ConnectionInfo::system_file_path().to_str().unwrap().to_string(),
host_addr: host_addr,
host_port: host_port,
host_retry: host_retry,
}
)
}

/// Getter Hostname
///
pub fn host_addr(&self) -> &String {
&self.host_addr
}

/// Getter Port
///
pub fn host_port(&self) -> u16 {
self.host_port
}

/// Save content into the connection file
///
pub fn save_to_file(&self) -> Result<(), std::io::Error> {
// Create the JSON object
let json_obj = json!({
"host": {
"addr": self.host_addr,
"port": self.host_port,
"retry": self.host_retry,
}
});

// Write new file
let mut file = File::create(&self.file_path)?;
let json_string = json_obj.to_string();
file.write_all(json_string.as_bytes())?;
Ok(())
}

}

39 changes: 39 additions & 0 deletions src/platform/connection_info/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use super::ConnectionInfo;
use serde_json::json;
use serde_json::Value as JsonValue;

// ----------------------------------------------------------------------------
#[test]
fn build_from_json_value_ok() {
let input = json!({
"host": {
"addr": "192.168.1.1",
"port": 5555,
}
});
let output = ConnectionInfo::build_from_json_value(input);
assert_eq!(output.is_err(), false);
let ci = output.unwrap();
assert_eq!(ci.host_addr(), "192.168.1.1");
assert_eq!(ci.host_port(), 5555);
}

// ----------------------------------------------------------------------------
#[test]
fn build_from_json_value_fail_0() {
let input = JsonValue::Null;
let output = ConnectionInfo::build_from_json_value(input);
assert_eq!(output.is_err(), true);
}

// ----------------------------------------------------------------------------
#[test]
fn build_from_json_value_fail_1() {
let input = json!({
"hostname": "localhost",
"port": 1883
});
let output = ConnectionInfo::build_from_json_value(input);
assert_eq!(output.is_err(), true);
}

Loading

0 comments on commit afd318e

Please sign in to comment.