p2panda_rs/schema/system/
schema_views.rs1use std::convert::TryFrom;
4
5use crate::document::{DocumentView, DocumentViewId};
6use crate::operation::{OperationValue, PinnedRelationList};
7use crate::schema::system::SystemSchemaError;
8use crate::schema::FieldType;
9
10#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct SchemaView {
15 id: DocumentViewId,
17
18 name: String,
20
21 description: String,
23
24 fields: PinnedRelationList,
26}
27
28#[allow(dead_code)] impl SchemaView {
30 pub fn view_id(&self) -> &DocumentViewId {
32 &self.id
33 }
34
35 pub fn name(&self) -> &str {
37 &self.name
38 }
39
40 pub fn description(&self) -> &str {
42 &self.description
43 }
44
45 pub fn fields(&self) -> &PinnedRelationList {
47 &self.fields
48 }
49}
50
51impl TryFrom<DocumentView> for SchemaView {
52 type Error = SystemSchemaError;
53
54 fn try_from(document_view: DocumentView) -> Result<Self, Self::Error> {
55 if document_view.len() != 3 {
56 return Err(SystemSchemaError::AdditionalFields);
57 };
58
59 let name = match document_view.get("name") {
60 Some(document_view_value) => {
61 if let OperationValue::String(value) = document_view_value.value() {
62 Ok(value)
63 } else {
64 Err(SystemSchemaError::InvalidField(
65 "name".into(),
66 document_view_value.clone(),
67 ))
68 }
69 }
70 None => Err(SystemSchemaError::MissingField("name".into())),
71 }?;
72
73 let description = match document_view.get("description") {
74 Some(document_view_value) => {
75 if let OperationValue::String(value) = document_view_value.value() {
76 Ok(value)
77 } else {
78 Err(SystemSchemaError::InvalidField(
79 "description".into(),
80 document_view_value.clone(),
81 ))
82 }
83 }
84 None => Err(SystemSchemaError::MissingField("description".into())),
85 }?;
86
87 let fields = match document_view.get("fields") {
88 Some(document_view_value) => {
89 if let OperationValue::PinnedRelationList(value) = document_view_value.value() {
90 Ok(value)
91 } else {
92 Err(SystemSchemaError::InvalidField(
93 "fields".into(),
94 document_view_value.clone(),
95 ))
96 }
97 }
98 None => Err(SystemSchemaError::MissingField("fields".into())),
99 }?;
100
101 Ok(Self {
102 id: document_view.id().clone(),
103 name: name.to_string(),
104 description: description.to_string(),
105 fields: fields.to_owned(),
106 })
107 }
108}
109
110#[derive(Clone, Debug, Eq, PartialEq)]
114pub struct SchemaFieldView {
115 id: DocumentViewId,
117
118 name: String,
120
121 field_type: FieldType,
123}
124
125#[allow(dead_code)] impl SchemaFieldView {
127 pub fn id(&self) -> &DocumentViewId {
129 &self.id
130 }
131
132 pub fn name(&self) -> &str {
134 &self.name
135 }
136
137 pub fn field_type(&self) -> &FieldType {
139 &self.field_type
140 }
141}
142
143impl TryFrom<DocumentView> for SchemaFieldView {
144 type Error = SystemSchemaError;
145
146 fn try_from(document_view: DocumentView) -> Result<Self, Self::Error> {
147 if document_view.len() != 2 {
148 return Err(SystemSchemaError::AdditionalFields);
149 };
150
151 let name = match document_view.get("name") {
152 Some(document_view_value) => {
153 if let OperationValue::String(value) = document_view_value.value() {
154 Ok(value)
155 } else {
156 Err(SystemSchemaError::InvalidField(
157 "name".into(),
158 document_view_value.clone(),
159 ))
160 }
161 }
162 None => Err(SystemSchemaError::MissingField("name".into())),
163 }?;
164
165 let field_type = match document_view.get("type") {
166 Some(document_view_value) => {
167 if let OperationValue::String(type_str) = document_view_value.value() {
168 Ok(type_str.parse::<FieldType>()?)
170 } else {
171 Err(SystemSchemaError::InvalidField(
172 "type".into(),
173 document_view_value.to_owned(),
174 ))
175 }
176 }
177 None => Err(SystemSchemaError::MissingField("type".to_string())),
178 }?;
179
180 Ok(Self {
181 id: document_view.id().clone(),
182 name: name.to_string(),
183 field_type,
184 })
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use std::convert::TryFrom;
191
192 use rstest::rstest;
193
194 use crate::document::{DocumentView, DocumentViewFields, DocumentViewId, DocumentViewValue};
195 use crate::operation::{OperationId, OperationValue, PinnedRelationList};
196 use crate::schema::system::SchemaFieldView;
197 use crate::schema::SchemaId;
198 use crate::test_utils::fixtures::schema_id;
199 use crate::test_utils::fixtures::{document_view_id, random_operation_id};
200
201 use super::{FieldType, SchemaView};
202
203 #[rstest]
204 fn from_document_view(
205 #[from(random_operation_id)] operation_id: OperationId,
206 #[from(random_operation_id)] relation: OperationId,
207 #[from(random_operation_id)] view_id: OperationId,
208 ) {
209 let mut venue_schema = DocumentViewFields::new();
210 venue_schema.insert(
211 "name",
212 DocumentViewValue::new(
213 &operation_id,
214 &OperationValue::String("venue_name".to_string()),
215 ),
216 );
217 venue_schema.insert(
218 "description",
219 DocumentViewValue::new(
220 &operation_id,
221 &OperationValue::String("Describes a venue".to_string()),
222 ),
223 );
224 venue_schema.insert(
225 "fields",
226 DocumentViewValue::new(
227 &operation_id,
228 &OperationValue::PinnedRelationList(PinnedRelationList::new(vec![
229 DocumentViewId::new(&[relation]),
230 ])),
231 ),
232 );
233 let document_view_id = DocumentViewId::from(view_id);
234 let document_view = DocumentView::new(&document_view_id, &venue_schema);
235
236 assert!(SchemaView::try_from(document_view).is_ok());
237 }
238
239 #[rstest]
240 fn field_type_from_document_view(
241 #[from(random_operation_id)] operation_id: OperationId,
242 document_view_id: DocumentViewId,
243 #[from(schema_id)] address_schema: SchemaId,
244 ) {
245 let mut bool_field = DocumentViewFields::new();
249 bool_field.insert(
250 "name",
251 DocumentViewValue::new(
252 &operation_id,
253 &OperationValue::String("is_accessible".to_string()),
254 ),
255 );
256 bool_field.insert(
257 "type",
258 DocumentViewValue::new(&operation_id, &FieldType::Boolean.into()),
259 );
260
261 let document_view = DocumentView::new(&document_view_id, &bool_field);
262 let field_view = SchemaFieldView::try_from(document_view);
263 assert!(field_view.is_ok());
264
265 let field_view = field_view.unwrap();
266 assert_eq!(field_view.field_type(), &FieldType::Boolean);
267 assert_eq!(field_view.name(), "is_accessible");
268
269 let mut capacity_field = DocumentViewFields::new();
273 capacity_field.insert(
274 "name",
275 DocumentViewValue::new(
276 &operation_id,
277 &OperationValue::String("capacity".to_string()),
278 ),
279 );
280 capacity_field.insert(
281 "type",
282 DocumentViewValue::new(&operation_id, &FieldType::Integer.into()),
283 );
284
285 let document_view = DocumentView::new(&document_view_id, &capacity_field);
286 let field_view = SchemaFieldView::try_from(document_view);
287 assert!(field_view.is_ok());
288 assert_eq!(field_view.unwrap().field_type(), &FieldType::Integer);
289
290 let mut float_field = DocumentViewFields::new();
294 float_field.insert(
295 "name",
296 DocumentViewValue::new(
297 &operation_id,
298 &OperationValue::String("ticket_price".to_string()),
299 ),
300 );
301 float_field.insert(
302 "type",
303 DocumentViewValue::new(&operation_id, &FieldType::Float.into()),
304 );
305
306 let document_view = DocumentView::new(&document_view_id, &float_field);
307 let field_view = SchemaFieldView::try_from(document_view);
308 assert!(field_view.is_ok());
309 assert_eq!(field_view.unwrap().field_type(), &FieldType::Float);
310
311 let mut str_field = DocumentViewFields::new();
315 str_field.insert(
316 "name",
317 DocumentViewValue::new(
318 &operation_id,
319 &OperationValue::String("venue_name".to_string()),
320 ),
321 );
322 str_field.insert(
323 "type",
324 DocumentViewValue::new(&operation_id, &FieldType::String.into()),
325 );
326
327 let document_view = DocumentView::new(&document_view_id, &str_field);
328 let field_view = SchemaFieldView::try_from(document_view);
329 assert!(field_view.is_ok());
330 assert_eq!(field_view.unwrap().field_type(), &FieldType::String);
331
332 let mut relation_field = DocumentViewFields::new();
336 relation_field.insert(
337 "name",
338 DocumentViewValue::new(
339 &operation_id,
340 &OperationValue::String("address".to_string()),
341 ),
342 );
343 relation_field.insert(
344 "type",
345 DocumentViewValue::new(
346 &operation_id,
347 &FieldType::Relation(address_schema.clone()).into(),
348 ),
349 );
350
351 let document_view = DocumentView::new(&document_view_id, &relation_field);
352 let field_view = SchemaFieldView::try_from(document_view);
353 assert!(field_view.is_ok());
354 assert_eq!(
355 field_view.unwrap().field_type(),
356 &FieldType::Relation(address_schema)
357 );
358 }
359
360 #[rstest]
361 fn invalid_schema_field(
362 #[from(random_operation_id)] operation_id: OperationId,
363 document_view_id: DocumentViewId,
364 ) {
365 let mut invalid_field = DocumentViewFields::new();
366 invalid_field.insert(
367 "name",
368 DocumentViewValue::new(
369 &operation_id,
370 &OperationValue::String("address".to_string()),
371 ),
372 );
373 invalid_field.insert(
374 "type",
375 DocumentViewValue::new(&operation_id, &OperationValue::String("hash".to_string())),
376 );
377
378 let document_view = DocumentView::new(&document_view_id, &invalid_field);
379 let field_view = SchemaFieldView::try_from(document_view);
380 assert!(field_view.is_err());
381 }
382
383 #[rstest]
384 fn too_many_fields(
385 #[from(random_operation_id)] operation_id: OperationId,
386 document_view_id: DocumentViewId,
387 ) {
388 let mut invalid_field = DocumentViewFields::new();
389 invalid_field.insert(
390 "name",
391 DocumentViewValue::new(
392 &operation_id,
393 &OperationValue::String("address".to_string()),
394 ),
395 );
396 invalid_field.insert(
397 "type",
398 DocumentViewValue::new(&operation_id, &OperationValue::String("hash".to_string())),
399 );
400 invalid_field.insert(
401 "monkey",
402 DocumentViewValue::new(&operation_id, &OperationValue::String("monkey".to_string())),
403 );
404 invalid_field.insert(
405 "penguin",
406 DocumentViewValue::new(
407 &operation_id,
408 &OperationValue::String("penguin".to_string()),
409 ),
410 );
411
412 let document_view = DocumentView::new(&document_view_id, &invalid_field);
413 let field_view = SchemaFieldView::try_from(document_view);
414 assert!(field_view.is_err());
415
416 let document_view = DocumentView::new(&document_view_id, &invalid_field);
417 let field_view = SchemaView::try_from(document_view);
418 assert!(field_view.is_err());
419 }
420}