use crate::{
parser::syntax_fragments::*,
sap_annotations::SAPAnnotationsProperty,
utils::{de_str_to_bool, default_false, default_true, odata_name_to_rust_safe_name, to_pascal_case},
};
use serde::{Deserialize, Serialize};
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Property {
#[serde(rename = "@Name")]
pub odata_name: String,
#[serde(rename = "@Type")]
pub edm_type: String,
#[serde(rename = "@Nullable", default = "default_true")]
pub nullable: bool,
#[serde(rename = "@MaxLength")]
pub max_length: Option<u16>,
#[serde(rename = "@Precision")]
pub precision: Option<u16>,
#[serde(rename = "@Scale")]
pub scale: Option<u16>,
#[serde(rename = "@ConcurrencyMode")]
pub concurrency_mode: Option<String>,
#[serde(
rename = "@FC_KeepInContent",
deserialize_with = "de_str_to_bool",
default = "default_false"
)]
pub fc_keep_in_content: bool,
#[serde(rename = "@FC_TargetPath")]
pub fc_target_path: Option<String>,
#[serde(flatten)]
pub sap_annotations: SAPAnnotationsProperty,
#[serde(skip, default)]
pub custom_deserializer: &'static str,
}
impl Property {
fn trim_prefix<'de>(some_str: &'de str, some_prefix: &str) -> &'de str {
if let Some(suffix) = some_str.strip_prefix(some_prefix) {
suffix
} else {
some_str
}
}
fn maybe_optional(&self, rust_type: &[u8]) -> Vec<u8> {
if self.nullable {
[OPTION, OPEN_ANGLE, rust_type, CLOSE_ANGLE].concat().to_vec()
} else {
rust_type.to_vec()
}
}
pub fn trim_complex_type_name<'a>(type_name: &'a str, namespace: &'a str) -> Vec<u8> {
let trimmed = Property::trim_prefix(type_name, namespace);
let trimmed = Property::trim_prefix(trimmed, ".");
let trimmed = Property::trim_prefix(trimmed, "CT_");
convert_case::Casing::to_case(&trimmed, convert_case::Case::Pascal)
.as_bytes()
.to_vec()
}
fn to_rust_type<'a>(&mut self, namespace: &str) -> Vec<u8> {
let type_bytes: Vec<u8> = if self.edm_type.starts_with("Edm.") {
match self.edm_type.as_ref() {
"Edm.Null" => UNIT.to_vec(),
"Edm.Binary" => self.maybe_optional(VECTOR_U8),
"Edm.Boolean" => self.maybe_optional(BOOLEAN),
"Edm.Byte" => U8.to_vec(),
"Edm.DateTime" => {
self.custom_deserializer = if self.nullable { SERDE_DE_DATETIME_OPT } else { SERDE_DE_DATETIME };
self.maybe_optional(NAIVE_DATE_TIME)
},
"Edm.DateTimeOffset" => {
self.custom_deserializer = if self.nullable { SERDE_DE_DATETIME_OPT } else { SERDE_DE_DATETIME };
self.maybe_optional(NAIVE_DATE_TIME)
},
"Edm.Decimal" => DECIMAL.to_vec(),
"Edm.Double" => F64.to_vec(),
"Edm.Single" => F32.to_vec(),
"Edm.Guid" => UUID.to_vec(),
"Edm.SByte" => self.maybe_optional(I8),
"Edm.Int16" => self.maybe_optional(I16),
"Edm.Int32" => self.maybe_optional(I32),
"Edm.Int64" => self.maybe_optional(I64),
"Edm.Time" => self.maybe_optional(STD_TIME_SYSTEMTIME),
_ => self.maybe_optional(STRING),
}
} else {
Property::trim_complex_type_name(&self.edm_type, namespace)
};
type_bytes.to_vec()
}
pub fn to_rust(&mut self, namespace: &str) -> Vec<u8> {
let mut response: Vec<u8> = Vec::new();
if !to_pascal_case(&self.odata_name).eq(&self.odata_name) {
response.extend(
[
SERDE_RENAME,
self.odata_name.clone().as_bytes(),
DOUBLE_QUOTE,
CLOSE_PAREN,
CLOSE_SQR,
LINE_FEED,
]
.concat(),
)
}
let rust_safe_name = odata_name_to_rust_safe_name(&self.odata_name);
let rust_type = &self.to_rust_type(namespace);
if !self.custom_deserializer.is_empty() {
response.extend(deserialize_with(self.custom_deserializer))
}
response.extend([PUBLIC, SPACE, rust_safe_name.as_bytes(), COLON, rust_type, COMMA, LINE_FEED].concat());
response
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct PropertyRef {
#[serde(rename = "@Name")]
pub name: String,
}
#[cfg(test)]
pub mod unit_tests;