pub mod linked_resource;
pub mod simple_resource;
pub mod simple_resource_linked;
use crate::grpc::server::{Id, IdList, Ids};
use crate::postgres::PsqlJsonValue;
use crate::{common::ArrErr, grpc::GrpcDataObjectType};
use core::fmt::Debug;
use log::error;
use std::collections::HashMap;
use tokio_postgres::types::Type as PsqlFieldType;
use uuid::Uuid;
pub trait Resource
where
Self: Sized,
{
fn get_definition() -> ResourceDefinition;
fn get_enum_string_val(field: &str, value: i32) -> Option<String> {
let _field = field;
let _value = value;
None
}
fn get_table_indices() -> Vec<String> {
vec![]
}
fn has_id_col(id_col: &str) -> bool {
for col in Self::get_definition().get_psql_id_cols() {
if col == id_col {
return true;
}
}
false
}
fn get_psql_table() -> String {
Self::get_definition().get_psql_table()
}
}
pub trait ObjectType<T>
where
Self: Resource,
T: GrpcDataObjectType,
{
fn get_ids(&self) -> Option<HashMap<String, String>> {
None
}
fn get_data(&self) -> Option<T> {
None
}
fn set_ids(&mut self, ids: HashMap<String, String>);
fn set_data(&mut self, data: T);
fn try_get_data(&self) -> Result<T, ArrErr> {
match self.get_data() {
Some(data) => Ok(data),
None => {
let error = "No data provided for ObjectType<T>.".to_string();
error!("(try_get_data) {}", error);
Err(ArrErr::Error(error))
}
}
}
fn try_get_uuids(&self) -> Result<HashMap<String, Uuid>, ArrErr> {
match self.get_ids() {
Some(ids) => {
let mut result = HashMap::new();
for (field, id) in ids {
let uuid = Uuid::parse_str(&id)?;
result.insert(field, uuid);
}
Ok(result)
}
None => {
let error = format!(
"No ids configured for resource [{}].",
Self::get_psql_table()
);
error!("(try_get_uuids) {}", error);
Err(ArrErr::Error(error))
}
}
}
fn get_value_for_id_field(&self, id_field: &str) -> Option<String> {
match self.get_ids() {
Some(map) => map.get(id_field).cloned(),
None => None,
}
}
}
#[derive(Clone, Debug)]
pub struct ResourceDefinition {
pub psql_table: String,
pub psql_id_cols: Vec<String>,
pub fields: HashMap<String, FieldDefinition>,
}
impl ResourceDefinition {
pub fn get_psql_table(&self) -> String {
self.psql_table.clone()
}
pub fn get_psql_id_cols(&self) -> Vec<String> {
self.psql_id_cols.clone()
}
pub fn has_field(&self, field: &str) -> bool {
self.fields.contains_key(field)
}
pub fn try_get_field(&self, field: &str) -> Result<&FieldDefinition, ArrErr> {
match self.fields.get(field) {
Some(field) => Ok(field),
None => Err(ArrErr::Error(format!(
"Tried to get field [{}] for table [{}], but the field does not exist.",
field, self.psql_table
))),
}
}
}
#[derive(Clone, Debug)]
pub struct ResourceObject<T>
where
T: GrpcDataObjectType + prost::Message,
{
pub ids: Option<HashMap<String, String>>,
pub data: Option<T>,
pub mask: Option<::prost_types::FieldMask>,
}
impl<T: GrpcDataObjectType + prost::Message> ObjectType<T> for ResourceObject<T>
where
Self: Resource,
{
fn get_ids(&self) -> Option<HashMap<String, String>> {
self.ids.clone()
}
fn set_ids(&mut self, ids: HashMap<String, String>) {
self.ids = Some(ids)
}
fn get_data(&self) -> Option<T> {
self.data.clone()
}
fn set_data(&mut self, data: T) {
self.data = Some(data)
}
}
#[derive(Clone, Debug)]
pub struct FieldDefinition {
pub field_type: PsqlFieldType,
mandatory: bool,
internal: bool,
read_only: bool,
default: Option<String>,
}
impl FieldDefinition {
pub fn new(field_type: PsqlFieldType, mandatory: bool) -> Self {
Self {
field_type,
mandatory,
internal: false,
read_only: false,
default: None,
}
}
pub fn new_internal(field_type: PsqlFieldType, mandatory: bool) -> Self {
Self {
field_type,
mandatory,
internal: true,
read_only: true,
default: None,
}
}
pub fn new_read_only(field_type: PsqlFieldType, mandatory: bool) -> Self {
Self {
field_type,
mandatory,
internal: false,
read_only: true,
default: None,
}
}
pub fn is_mandatory(&self) -> bool {
self.mandatory
}
pub fn is_internal(&self) -> bool {
self.internal
}
pub fn is_read_only(&self) -> bool {
self.read_only
}
pub fn has_default(&self) -> bool {
self.default.is_some()
}
pub fn set_default(&mut self, default: String) -> Self {
self.default = Some(default);
self.clone()
}
pub fn get_default(&self) -> String {
if self.has_default() {
self.default.clone().unwrap_or_else(|| String::from("NULL"))
} else {
panic!("get_default called on a field without a default value");
}
}
}
impl TryFrom<Id> for Uuid {
type Error = ArrErr;
fn try_from(id: Id) -> Result<Self, ArrErr> {
Uuid::try_parse(&id.id).map_err(ArrErr::UuidError)
}
}
impl TryFrom<IdList> for Vec<Uuid> {
type Error = ArrErr;
fn try_from(list: IdList) -> Result<Self, ArrErr> {
let mut uuid_list = vec![];
for id in list.ids.iter() {
uuid_list.push(Uuid::try_parse(id).map_err(ArrErr::UuidError)?);
}
Ok(uuid_list)
}
}
impl TryFrom<Ids> for HashMap<String, Uuid> {
type Error = ArrErr;
fn try_from(ids: Ids) -> Result<Self, ArrErr> {
let mut uuid_hash = HashMap::new();
for id in ids.ids.iter() {
uuid_hash.insert(
id.field.clone(),
Uuid::try_parse(&id.value).map_err(ArrErr::UuidError)?,
);
}
Ok(uuid_hash)
}
}
impl TryFrom<PsqlJsonValue> for Vec<u32> {
type Error = ArrErr;
fn try_from(json_value: PsqlJsonValue) -> Result<Self, ArrErr> {
match json_value.value.as_array() {
Some(arr) => {
let iter = arr.iter();
let mut vec: Vec<u32> = vec![];
for val in iter {
vec.push(val.as_u64().ok_or(ArrErr::Error(format!(
"json_value did not contain array with u32: {}",
json_value.value
)))? as u32);
}
Ok(vec)
}
None => {
let error = format!(
"Could not convert [PsqlJsonValue] to [Vec<u32>]: {:?}",
json_value
);
error!("(try_from) {}", error);
Err(ArrErr::Error(error))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio_postgres::types::Type as PsqlFieldType;
#[test]
fn test_field_definition_new() {
let field_type = PsqlFieldType::VARCHAR;
let mandatory = true;
let field_def = FieldDefinition::new(field_type.clone(), mandatory);
assert_eq!(field_def.field_type, field_type);
assert_eq!(field_def.is_mandatory(), mandatory);
assert!(!field_def.is_internal());
assert!(!field_def.is_read_only());
assert!(!field_def.has_default());
}
#[test]
fn test_field_definition_internal_field() {
let field_type = PsqlFieldType::FLOAT8;
let mandatory = false;
let field_def = FieldDefinition::new_internal(field_type.clone(), mandatory);
assert_eq!(field_def.field_type, field_type);
assert_eq!(field_def.is_mandatory(), mandatory);
assert!(field_def.is_internal());
assert!(field_def.is_read_only());
assert!(!field_def.has_default());
}
#[test]
fn test_field_definition_read_only_field() {
let field_type = PsqlFieldType::FLOAT8;
let mandatory = false;
let field_def = FieldDefinition::new_read_only(field_type.clone(), mandatory);
assert_eq!(field_def.field_type, field_type);
assert_eq!(field_def.is_mandatory(), mandatory);
assert!(!field_def.is_internal());
assert!(field_def.is_read_only());
assert!(!field_def.has_default());
}
#[test]
fn test_field_definition_set_default() {
let field_type = PsqlFieldType::BOOL;
let mandatory = true;
let mut field_def = FieldDefinition::new(field_type, mandatory);
assert!(!field_def.has_default());
let default_value = "true".to_owned();
field_def.set_default(default_value.clone());
assert!(field_def.has_default());
assert_eq!(field_def.get_default(), default_value);
}
#[test]
#[should_panic(expected = "get_default called on a field without a default value")]
fn test_field_definition_get_default_without_default() {
let field_type = PsqlFieldType::TEXT;
let mandatory = false;
let field_def = FieldDefinition::new_internal(field_type, mandatory);
field_def.get_default();
}
#[test]
fn test_field_definition_get_default_with_default() {
let field_type = PsqlFieldType::FLOAT4;
let mandatory = false;
let default_value = "3.14".to_owned();
let mut field_def = FieldDefinition::new(field_type, mandatory);
field_def.set_default(default_value.clone());
assert_eq!(field_def.get_default(), default_value);
}
#[test]
fn test_resource_definition_get_psql_table() {
let psql_table = "my_table".to_owned();
let resource_def = ResourceDefinition {
psql_table: psql_table.clone(),
psql_id_cols: Vec::new(),
fields: HashMap::new(),
};
assert_eq!(resource_def.get_psql_table(), psql_table);
}
#[test]
fn test_resource_definition_get_psql_id_cols() {
let psql_id_cols = vec!["id".to_owned(), "name".to_owned()];
let resource_def = ResourceDefinition {
psql_table: String::new(),
psql_id_cols: psql_id_cols.clone(),
fields: HashMap::new(),
};
assert_eq!(resource_def.get_psql_id_cols(), psql_id_cols);
}
#[test]
fn test_resource_definition_has_field() {
let field_name = "field1";
let field_def = FieldDefinition::new(PsqlFieldType::TEXT, true);
let mut fields = HashMap::new();
fields.insert(field_name.to_owned(), field_def);
let resource_def = ResourceDefinition {
psql_table: String::new(),
psql_id_cols: Vec::new(),
fields,
};
assert!(resource_def.has_field(field_name));
assert!(!resource_def.has_field("nonexistent_field"));
}
#[test]
fn test_resource_definition_try_get_field() {
let field_name = "field1";
let field_def = FieldDefinition::new(PsqlFieldType::TEXT, true);
let mut fields = HashMap::new();
fields.insert(field_name.to_owned(), field_def.clone());
let resource_def = ResourceDefinition {
psql_table: String::from("test"),
psql_id_cols: vec![String::from("test_id")],
fields,
};
let result = resource_def.try_get_field(field_name);
assert!(result.is_ok());
assert!(matches!(result.unwrap(), _field_def));
let result = resource_def.try_get_field("nonexistent_field");
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
format!(
"error: Tried to get field [nonexistent_field] for table [{}], but the field does not exist.", resource_def.get_psql_table()
)
);
}
}