parse_rust_schema/
storage_format.rs1use parse_rust_storage::FieldType;
21
22pub const NON_FIELD_KEYS: [&str; 3] = ["_id", "_metadata", "_client_permissions"];
24
25pub fn field_type_to_storage(ty: &FieldType) -> String {
30 match ty {
31 FieldType::Pointer { target_class } => format!("*{target_class}"),
32 FieldType::Relation { target_class } => format!("relation<{target_class}>"),
33 FieldType::Number => "number".into(),
34 FieldType::String => "string".into(),
35 FieldType::Boolean => "boolean".into(),
36 FieldType::Date => "date".into(),
37 FieldType::Object => "object".into(),
38 FieldType::Array => "array".into(),
39 FieldType::GeoPoint => "geopoint".into(),
40 FieldType::File => "file".into(),
41 FieldType::Bytes => "bytes".into(),
42 FieldType::Polygon => "polygon".into(),
43 FieldType::Acl => String::new(),
49 }
50}
51
52pub fn storage_to_field_type(s: &str) -> Option<FieldType> {
58 if let Some(target) = s.strip_prefix('*') {
59 return Some(FieldType::Pointer {
60 target_class: target.to_string(),
61 });
62 }
63 if let Some(rest) = s.strip_prefix("relation<") {
64 return rest.strip_suffix('>').map(|target| FieldType::Relation {
65 target_class: target.to_string(),
66 });
67 }
68 Some(match s {
69 "number" => FieldType::Number,
70 "string" => FieldType::String,
71 "boolean" => FieldType::Boolean,
72 "date" => FieldType::Date,
73 "map" | "object" => FieldType::Object,
76 "array" => FieldType::Array,
77 "geopoint" => FieldType::GeoPoint,
78 "file" => FieldType::File,
79 "bytes" => FieldType::Bytes,
80 "polygon" => FieldType::Polygon,
81 _ => return None,
82 })
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn acl_is_not_storable() {
91 assert!(
92 field_type_to_storage(&FieldType::Acl).is_empty(),
93 "ACL must never be written into _SCHEMA"
94 );
95 }
96
97 #[test]
98 fn every_type_round_trips() {
99 let types = [
100 FieldType::Number,
101 FieldType::String,
102 FieldType::Boolean,
103 FieldType::Date,
104 FieldType::Object,
105 FieldType::Array,
106 FieldType::GeoPoint,
107 FieldType::File,
108 FieldType::Bytes,
109 FieldType::Polygon,
110 FieldType::Pointer {
111 target_class: "_User".into(),
112 },
113 FieldType::Relation {
114 target_class: "Post".into(),
115 },
116 ];
117 for ty in types {
118 let s = field_type_to_storage(&ty);
119 assert_eq!(
120 storage_to_field_type(&s).as_ref(),
121 Some(&ty),
122 "{ty:?} did not round trip through {s:?}"
123 );
124 }
125 }
126
127 #[test]
130 fn storage_strings_are_byte_exact() {
131 assert_eq!(field_type_to_storage(&FieldType::String), "string");
132 assert_eq!(field_type_to_storage(&FieldType::Number), "number");
133 assert_eq!(field_type_to_storage(&FieldType::Boolean), "boolean");
134 assert_eq!(field_type_to_storage(&FieldType::Date), "date");
135 assert_eq!(field_type_to_storage(&FieldType::Object), "object");
136 assert_eq!(field_type_to_storage(&FieldType::GeoPoint), "geopoint");
137 assert_eq!(
138 field_type_to_storage(&FieldType::Pointer {
139 target_class: "_User".into()
140 }),
141 "*_User",
142 "a pointer is an asterisk and the class name, with no separator"
143 );
144 assert_eq!(
145 field_type_to_storage(&FieldType::Relation {
146 target_class: "Post".into()
147 }),
148 "relation<Post>"
149 );
150 }
151
152 #[test]
153 fn the_legacy_map_spelling_still_reads() {
154 assert_eq!(storage_to_field_type("map"), Some(FieldType::Object));
156 assert_eq!(storage_to_field_type("object"), Some(FieldType::Object));
157 }
158
159 #[test]
161 fn an_unknown_type_string_reads_as_nothing_rather_than_erroring() {
162 assert_eq!(storage_to_field_type("vector"), None);
163 assert_eq!(storage_to_field_type("Number"), None, "case matters");
164 assert_eq!(storage_to_field_type(""), None);
165 assert_eq!(storage_to_field_type("relation<unterminated"), None);
167 }
168
169 #[test]
170 fn non_field_keys_are_the_upstream_set() {
171 assert_eq!(NON_FIELD_KEYS, ["_id", "_metadata", "_client_permissions"]);
172 }
173}