1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
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};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// Represents an `edm:Property` element
#[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(default = "default_true")]
pub nullable: bool,
pub max_length: Option<u16>,
pub precision: Option<u16>,
pub scale: Option<u16>,
pub concurrency_mode: Option<String>,
// Microsoft Annotations
#[serde(
rename = "m:FC_KeepInContent",
deserialize_with = "de_str_to_bool",
default = "default_false"
)]
pub fc_keep_in_content: bool,
#[serde(rename = "m:FC_TargetPath")]
pub fc_target_path: Option<String>,
// SAP Annotations
#[serde(flatten)]
pub sap_annotations: SAPAnnotationsProperty,
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// The `Cargo.toml` of the application consuming the code generated by this tool must declare at least the following
/// dependencies:
///
/// ```toml
/// [dependencies]
/// chrono = { version = "0.4", features = ["serde"]}
/// rust_decimal = "1.30"
/// serde = { version = "1.0", features = ["derive"] }
/// uuid = { version = "1.4", features = ["serde"]}
/// ```
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()
}
}
// For complex types, the type struct will already have been generated using the <ct_name> part extracted
// from the OData type name conforming to one of these patterns:
// <namespace>.CT_<ct_name>
// <namespace>.<ct_name>
// <ct_name>
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>(&self, namespace: &str) -> Vec<u8> {
// Handle complex types separately
let type_bytes: Vec<u8> = if self.edm_type.starts_with("Edm.") {
match self.edm_type.as_ref() {
// Although "Null" is listed as a valid EDM datatype, this type is excluded here as Rust has no means to
// represent this value
"Edm.Binary" => self.maybe_optional(VECTOR_U8),
"Edm.Boolean" => self.maybe_optional(BOOLEAN),
"Edm.Byte" => U8.to_vec(),
"Edm.DateTime" => self.maybe_optional(STRING),
"Edm.DateTimeOffset" => 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),
// If the type is none of the above, then assume it must be a string
_ => self.maybe_optional(STRING),
}
} else {
Property::trim_complex_type_name(&self.edm_type, namespace)
};
type_bytes.to_vec()
}
pub fn to_rust(&self, namespace: &str) -> Vec<u8> {
let mut response: Vec<u8> = Vec::new();
// Check whether the Pascal case name is correctly transformed into a snake_case name.
// If not, output a serde_rename directive.
// This catches deserialization problems with fields that contain capitalised abbreviations:
// E.G. "ID" instead of "Id"
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);
// Write struct field
response.extend(
[
PUBLIC,
SPACE,
rust_safe_name.as_bytes(),
COLON,
&self.to_rust_type(namespace),
COMMA,
LINE_FEED,
]
.concat(),
);
response
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct PropertyRef {
pub name: String,
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#[cfg(test)]
pub mod unit_tests;