use parse_rust_storage::FieldType;
pub const NON_FIELD_KEYS: [&str; 3] = ["_id", "_metadata", "_client_permissions"];
pub fn field_type_to_storage(ty: &FieldType) -> String {
match ty {
FieldType::Pointer { target_class } => format!("*{target_class}"),
FieldType::Relation { target_class } => format!("relation<{target_class}>"),
FieldType::Number => "number".into(),
FieldType::String => "string".into(),
FieldType::Boolean => "boolean".into(),
FieldType::Date => "date".into(),
FieldType::Object => "object".into(),
FieldType::Array => "array".into(),
FieldType::GeoPoint => "geopoint".into(),
FieldType::File => "file".into(),
FieldType::Bytes => "bytes".into(),
FieldType::Polygon => "polygon".into(),
FieldType::Acl => String::new(),
}
}
pub fn storage_to_field_type(s: &str) -> Option<FieldType> {
if let Some(target) = s.strip_prefix('*') {
return Some(FieldType::Pointer {
target_class: target.to_string(),
});
}
if let Some(rest) = s.strip_prefix("relation<") {
return rest.strip_suffix('>').map(|target| FieldType::Relation {
target_class: target.to_string(),
});
}
Some(match s {
"number" => FieldType::Number,
"string" => FieldType::String,
"boolean" => FieldType::Boolean,
"date" => FieldType::Date,
"map" | "object" => FieldType::Object,
"array" => FieldType::Array,
"geopoint" => FieldType::GeoPoint,
"file" => FieldType::File,
"bytes" => FieldType::Bytes,
"polygon" => FieldType::Polygon,
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn acl_is_not_storable() {
assert!(
field_type_to_storage(&FieldType::Acl).is_empty(),
"ACL must never be written into _SCHEMA"
);
}
#[test]
fn every_type_round_trips() {
let types = [
FieldType::Number,
FieldType::String,
FieldType::Boolean,
FieldType::Date,
FieldType::Object,
FieldType::Array,
FieldType::GeoPoint,
FieldType::File,
FieldType::Bytes,
FieldType::Polygon,
FieldType::Pointer {
target_class: "_User".into(),
},
FieldType::Relation {
target_class: "Post".into(),
},
];
for ty in types {
let s = field_type_to_storage(&ty);
assert_eq!(
storage_to_field_type(&s).as_ref(),
Some(&ty),
"{ty:?} did not round trip through {s:?}"
);
}
}
#[test]
fn storage_strings_are_byte_exact() {
assert_eq!(field_type_to_storage(&FieldType::String), "string");
assert_eq!(field_type_to_storage(&FieldType::Number), "number");
assert_eq!(field_type_to_storage(&FieldType::Boolean), "boolean");
assert_eq!(field_type_to_storage(&FieldType::Date), "date");
assert_eq!(field_type_to_storage(&FieldType::Object), "object");
assert_eq!(field_type_to_storage(&FieldType::GeoPoint), "geopoint");
assert_eq!(
field_type_to_storage(&FieldType::Pointer {
target_class: "_User".into()
}),
"*_User",
"a pointer is an asterisk and the class name, with no separator"
);
assert_eq!(
field_type_to_storage(&FieldType::Relation {
target_class: "Post".into()
}),
"relation<Post>"
);
}
#[test]
fn the_legacy_map_spelling_still_reads() {
assert_eq!(storage_to_field_type("map"), Some(FieldType::Object));
assert_eq!(storage_to_field_type("object"), Some(FieldType::Object));
}
#[test]
fn an_unknown_type_string_reads_as_nothing_rather_than_erroring() {
assert_eq!(storage_to_field_type("vector"), None);
assert_eq!(storage_to_field_type("Number"), None, "case matters");
assert_eq!(storage_to_field_type(""), None);
assert_eq!(storage_to_field_type("relation<unterminated"), None);
}
#[test]
fn non_field_keys_are_the_upstream_set() {
assert_eq!(NON_FIELD_KEYS, ["_id", "_metadata", "_client_permissions"]);
}
}