1use std::collections::BTreeMap;
34use dpp::data_contract::accessors::v0::DataContractV0Getters;
35use dpp::data_contract::DataContract;
36use dpp::platform_value::Value;
37use dpp::state_transition::batch_transition::batched_transition::document_transition::DocumentTransitionV0Methods;
38use dpp::document::{Document, DocumentV0Getters};
39use dpp::state_transition::batch_transition::batched_transition::document_transition::DocumentTransition;
40use dpp::state_transition::batch_transition::document_create_transition::v0::v0_methods::DocumentCreateTransitionV0Methods;
41use dpp::state_transition::batch_transition::document_base_transition::v0::v0_methods::DocumentBaseTransitionV0Methods;
42use dpp::state_transition::batch_transition::document_replace_transition::v0::v0_methods::DocumentReplaceTransitionV0Methods;
43use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::v0::v0_methods::DocumentTransferTransitionV0Methods;
44use dpp::state_transition::batch_transition::batched_transition::document_update_price_transition::v0::v0_methods::DocumentUpdatePriceTransitionV0Methods;
45use crate::query::{InternalClauses, QuerySyntaxSimpleValidationResult, ValueClause, WhereOperator};
46use crate::error::query::QuerySyntaxError;
47use dpp::platform_value::ValueMapHelper;
48
49#[cfg(any(feature = "server", feature = "verify"))]
56#[derive(Debug, PartialEq, Clone)]
57pub struct DriveDocumentQueryFilter<'a> {
58 pub contract: &'a DataContract,
60 pub document_type_name: String,
62 pub action_clauses: DocumentActionMatchClauses,
64}
65
66#[cfg(any(feature = "server", feature = "verify"))]
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum TransitionCheckResult {
70 Pass,
72 Fail,
74 NeedsOriginal,
76}
77
78#[allow(clippy::large_enum_variant)]
89#[derive(Debug, PartialEq, Clone)]
90pub enum DocumentActionMatchClauses {
91 Create {
93 new_document_clauses: InternalClauses,
95 },
96 Replace {
98 original_document_clauses: InternalClauses,
100 new_document_clauses: InternalClauses,
102 },
103 Delete {
105 original_document_clauses: InternalClauses,
107 },
108 Transfer {
110 original_document_clauses: InternalClauses,
112 owner_clause: Option<ValueClause>,
114 },
115 UpdatePrice {
117 original_document_clauses: InternalClauses,
119 price_clause: Option<ValueClause>,
121 },
122 Purchase {
124 original_document_clauses: InternalClauses,
126 owner_clause: Option<ValueClause>,
128 },
129}
130
131impl DriveDocumentQueryFilter<'_> {
132 #[cfg(any(feature = "server", feature = "verify"))]
145 pub fn matches_document_transition(
146 &self,
147 document_transition: &DocumentTransition,
148 batch_owner_value: Option<&Value>, ) -> TransitionCheckResult {
150 if document_transition.base().data_contract_id() != self.contract.id()
152 || document_transition.base().document_type_name() != &self.document_type_name
153 {
154 return TransitionCheckResult::Fail;
155 }
156
157 let id_value: Value = document_transition.base().id().into();
159
160 match document_transition {
161 DocumentTransition::Create(create) => {
162 if let DocumentActionMatchClauses::Create {
163 new_document_clauses,
164 } = &self.action_clauses
165 {
166 if self.evaluate_clauses(new_document_clauses, &id_value, create.data()) {
167 TransitionCheckResult::Pass
168 } else {
169 TransitionCheckResult::Fail
170 }
171 } else {
172 TransitionCheckResult::Fail
173 }
174 }
175 DocumentTransition::Replace(replace) => {
176 if let DocumentActionMatchClauses::Replace {
177 original_document_clauses,
178 new_document_clauses,
179 } = &self.action_clauses
180 {
181 let final_ok = if new_document_clauses.is_empty() {
182 true
183 } else {
184 self.evaluate_clauses(new_document_clauses, &id_value, replace.data())
185 };
186 if !final_ok {
187 return TransitionCheckResult::Fail;
188 }
189 if original_document_clauses.is_empty() {
190 return TransitionCheckResult::Pass;
191 }
192 if original_document_clauses.is_for_primary_key() {
193 if self.evaluate_clauses(
194 original_document_clauses,
195 &id_value,
196 &BTreeMap::new(),
197 ) {
198 return TransitionCheckResult::Pass;
199 }
200 return TransitionCheckResult::Fail;
201 }
202 TransitionCheckResult::NeedsOriginal
203 } else {
204 TransitionCheckResult::Fail
205 }
206 }
207 DocumentTransition::Delete(_) => {
208 if let DocumentActionMatchClauses::Delete {
209 original_document_clauses,
210 } = &self.action_clauses
211 {
212 if original_document_clauses.is_empty() {
213 return TransitionCheckResult::Pass;
214 }
215 if original_document_clauses.is_for_primary_key() {
216 if self.evaluate_clauses(
217 original_document_clauses,
218 &id_value,
219 &BTreeMap::new(),
220 ) {
221 return TransitionCheckResult::Pass;
222 }
223 return TransitionCheckResult::Fail;
224 }
225 TransitionCheckResult::NeedsOriginal
226 } else {
227 TransitionCheckResult::Fail
228 }
229 }
230 DocumentTransition::Transfer(transfer) => {
231 if let DocumentActionMatchClauses::Transfer {
232 original_document_clauses,
233 owner_clause,
234 } = &self.action_clauses
235 {
236 let new_owner_value: Value = transfer.recipient_owner_id().into();
237 let owner_ok = match owner_clause {
238 Some(clause) => clause.matches_value(&new_owner_value),
239 None => true,
240 };
241 if !owner_ok {
242 return TransitionCheckResult::Fail;
243 }
244 if original_document_clauses.is_empty() {
245 return TransitionCheckResult::Pass;
246 }
247 if original_document_clauses.is_for_primary_key() {
248 if self.evaluate_clauses(
249 original_document_clauses,
250 &id_value,
251 &BTreeMap::new(),
252 ) {
253 return TransitionCheckResult::Pass;
254 }
255 return TransitionCheckResult::Fail;
256 }
257 TransitionCheckResult::NeedsOriginal
258 } else {
259 TransitionCheckResult::Fail
260 }
261 }
262 DocumentTransition::UpdatePrice(update_price) => {
263 if let DocumentActionMatchClauses::UpdatePrice {
264 original_document_clauses,
265 price_clause,
266 } = &self.action_clauses
267 {
268 let price_value = Value::U64(update_price.price());
269 let price_ok = match price_clause {
270 Some(clause) => clause.matches_value(&price_value),
271 None => true,
272 };
273 if !price_ok {
274 return TransitionCheckResult::Fail;
275 }
276 if original_document_clauses.is_empty() {
277 return TransitionCheckResult::Pass;
278 }
279 if original_document_clauses.is_for_primary_key() {
280 if self.evaluate_clauses(
281 original_document_clauses,
282 &id_value,
283 &BTreeMap::new(),
284 ) {
285 return TransitionCheckResult::Pass;
286 }
287 return TransitionCheckResult::Fail;
288 }
289 TransitionCheckResult::NeedsOriginal
290 } else {
291 TransitionCheckResult::Fail
292 }
293 }
294 DocumentTransition::Purchase(_) => {
295 if let DocumentActionMatchClauses::Purchase {
296 original_document_clauses,
297 owner_clause,
298 } = &self.action_clauses
299 {
300 let owner_ok = match (owner_clause, batch_owner_value) {
301 (Some(clause), Some(val)) => clause.matches_value(val),
302 (Some(_), None) => return TransitionCheckResult::Fail,
303 (None, _) => true,
304 };
305 if !owner_ok {
306 return TransitionCheckResult::Fail;
307 }
308 if original_document_clauses.is_empty() {
309 return TransitionCheckResult::Pass;
310 }
311 if original_document_clauses.is_for_primary_key() {
312 if self.evaluate_clauses(
313 original_document_clauses,
314 &id_value,
315 &BTreeMap::new(),
316 ) {
317 return TransitionCheckResult::Pass;
318 }
319 return TransitionCheckResult::Fail;
320 }
321 TransitionCheckResult::NeedsOriginal
322 } else {
323 TransitionCheckResult::Fail
324 }
325 }
326 }
327 }
328
329 #[cfg(any(feature = "server", feature = "verify"))]
337 pub fn matches_original_document(&self, original_document: &Document) -> bool {
338 match &self.action_clauses {
340 DocumentActionMatchClauses::Replace {
341 original_document_clauses,
342 ..
343 }
344 | DocumentActionMatchClauses::Delete {
345 original_document_clauses,
346 }
347 | DocumentActionMatchClauses::Transfer {
348 original_document_clauses,
349 ..
350 }
351 | DocumentActionMatchClauses::UpdatePrice {
352 original_document_clauses,
353 ..
354 }
355 | DocumentActionMatchClauses::Purchase {
356 original_document_clauses,
357 ..
358 } => {
359 let id_value: Value = original_document.id().into();
360 self.evaluate_clauses(
361 original_document_clauses,
362 &id_value,
363 original_document.properties(),
364 )
365 }
366 _ => false,
367 }
368 }
369
370 #[cfg(any(feature = "server", feature = "verify"))]
372 fn evaluate_clauses(
373 &self,
374 clauses: &InternalClauses,
375 document_id_value: &Value,
376 document_data: &BTreeMap<String, Value>,
377 ) -> bool {
378 if let Some(primary_key_in_clause) = &clauses.primary_key_in_clause {
380 if !primary_key_in_clause.matches_value(document_id_value) {
381 return false;
382 }
383 }
384
385 if let Some(primary_key_equal_clause) = &clauses.primary_key_equal_clause {
387 if !primary_key_equal_clause.matches_value(document_id_value) {
388 return false;
389 }
390 }
391
392 if let Some(in_clause) = &clauses.in_clause {
394 let field_value = get_value_by_path(document_data, &in_clause.field);
395 if let Some(value) = field_value {
396 if !in_clause.matches_value(value) {
397 return false;
398 }
399 } else {
400 return false;
401 }
402 }
403
404 if let Some(range_clause) = &clauses.range_clause {
406 let field_value = get_value_by_path(document_data, &range_clause.field);
407 if let Some(value) = field_value {
408 if !range_clause.matches_value(value) {
409 return false;
410 }
411 } else {
412 return false;
413 }
414 }
415
416 for (field, equal_clause) in &clauses.equal_clauses {
418 let field_value = get_value_by_path(document_data, field);
419 if let Some(value) = field_value {
420 if !equal_clause.matches_value(value) {
421 return false;
422 }
423 } else {
424 return false;
425 }
426 }
427
428 true
429 }
430
431 #[cfg(any(feature = "server", feature = "verify"))]
435 pub fn validate(&self) -> QuerySyntaxSimpleValidationResult {
436 let Some(document_type) = self
438 .contract
439 .document_type_optional_for_name(&self.document_type_name)
440 else {
441 return QuerySyntaxSimpleValidationResult::new_with_error(
442 QuerySyntaxError::DocumentTypeNotFound("unknown document type"),
443 );
444 };
445
446 match &self.action_clauses {
447 DocumentActionMatchClauses::Create {
448 new_document_clauses,
449 } => new_document_clauses.validate_against_schema(document_type),
450 DocumentActionMatchClauses::Replace {
451 original_document_clauses,
452 new_document_clauses,
453 } => {
454 if !original_document_clauses.is_empty() {
455 let result = original_document_clauses.validate_against_schema(document_type);
456 if result.is_err() {
457 return result;
458 }
459 }
460 if !new_document_clauses.is_empty() {
461 new_document_clauses.validate_against_schema(document_type)
462 } else {
463 QuerySyntaxSimpleValidationResult::new()
464 }
465 }
466 DocumentActionMatchClauses::Delete {
467 original_document_clauses,
468 } => original_document_clauses.validate_against_schema(document_type),
469 DocumentActionMatchClauses::Transfer {
470 original_document_clauses,
471 owner_clause,
472 } => {
473 if !original_document_clauses.is_empty() {
474 let result = original_document_clauses.validate_against_schema(document_type);
475 if result.is_err() {
476 return result;
477 }
478 }
479 if let Some(owner) = owner_clause {
480 let ok = match owner.operator {
481 WhereOperator::Equal => matches!(owner.value, Value::Identifier(_)),
482 WhereOperator::In => match &owner.value {
483 Value::Array(arr) => {
484 arr.iter().all(|v| matches!(v, Value::Identifier(_)))
485 }
486 _ => false,
487 },
488 _ => false,
489 };
490 if ok {
491 QuerySyntaxSimpleValidationResult::new()
492 } else {
493 QuerySyntaxSimpleValidationResult::new_with_error(
494 QuerySyntaxError::InvalidWhereClauseComponents("invalid owner clause"),
495 )
496 }
497 } else {
498 QuerySyntaxSimpleValidationResult::new()
499 }
500 }
501 DocumentActionMatchClauses::UpdatePrice {
502 original_document_clauses,
503 price_clause,
504 } => {
505 if !original_document_clauses.is_empty() {
506 let result = original_document_clauses.validate_against_schema(document_type);
507 if result.is_err() {
508 return result;
509 }
510 }
511 if let Some(price) = price_clause {
512 let ok = match price.operator {
513 WhereOperator::Equal
514 | WhereOperator::GreaterThan
515 | WhereOperator::GreaterThanOrEquals
516 | WhereOperator::LessThan
517 | WhereOperator::LessThanOrEquals => {
518 price.value.is_integer_can_fit_in_64_bits()
519 }
520 WhereOperator::Between
521 | WhereOperator::BetweenExcludeBounds
522 | WhereOperator::BetweenExcludeLeft
523 | WhereOperator::BetweenExcludeRight => match &price.value {
524 Value::Array(arr) => {
525 arr.len() == 2
526 && arr.iter().all(|v| v.is_integer_can_fit_in_64_bits())
527 && arr[0] < arr[1]
528 }
529 _ => false,
530 },
531 WhereOperator::In => match &price.value {
532 Value::Array(arr) => {
533 arr.iter().all(|v| v.is_integer_can_fit_in_64_bits())
534 }
535 _ => false,
536 },
537 WhereOperator::StartsWith => false,
538 };
539 if ok {
540 QuerySyntaxSimpleValidationResult::new()
541 } else {
542 QuerySyntaxSimpleValidationResult::new_with_error(
543 QuerySyntaxError::InvalidWhereClauseComponents("invalid price clause"),
544 )
545 }
546 } else {
547 QuerySyntaxSimpleValidationResult::new()
548 }
549 }
550 DocumentActionMatchClauses::Purchase {
551 original_document_clauses,
552 owner_clause,
553 } => {
554 if !original_document_clauses.is_empty() {
555 let result = original_document_clauses.validate_against_schema(document_type);
556 if result.is_err() {
557 return result;
558 }
559 }
560 if let Some(owner) = owner_clause {
561 let ok = match owner.operator {
562 WhereOperator::Equal => matches!(owner.value, Value::Identifier(_)),
563 WhereOperator::In => match &owner.value {
564 Value::Array(arr) => {
565 arr.iter().all(|v| matches!(v, Value::Identifier(_)))
566 }
567 _ => false,
568 },
569 _ => false,
570 };
571 if ok {
572 QuerySyntaxSimpleValidationResult::new()
573 } else {
574 QuerySyntaxSimpleValidationResult::new_with_error(
575 QuerySyntaxError::InvalidWhereClauseComponents("invalid owner clause"),
576 )
577 }
578 } else {
579 QuerySyntaxSimpleValidationResult::new()
580 }
581 }
582 }
583 }
584}
585
586#[cfg(any(feature = "server", feature = "verify"))]
593fn get_value_by_path<'a>(root: &'a BTreeMap<String, Value>, path: &str) -> Option<&'a Value> {
594 if path.is_empty() {
595 return None;
596 }
597 let mut current: Option<&Value> = None;
598 let mut segments = path.split('.');
599 if let Some(first) = segments.next() {
600 current = root.get(first);
601 }
602 for seg in segments {
603 match current {
604 Some(Value::Map(ref vm)) => {
605 current = vm.get_optional_key(seg);
606 }
607 _ => return None,
608 }
609 }
610 current
611}
612
613#[cfg(test)]
614mod tests {
615 use super::*;
616 use crate::query::{ValueClause, WhereClause, WhereOperator};
617 use dpp::document::{Document, DocumentV0};
618 use dpp::prelude::Identifier;
619 use dpp::state_transition::batch_transition::document_base_transition::v1::DocumentBaseTransitionV1;
620 use dpp::state_transition::batch_transition::document_base_transition::DocumentBaseTransition;
621 use dpp::tests::fixtures::get_data_contract_fixture;
622 use dpp::version::LATEST_PLATFORM_VERSION;
623
624 #[test]
625 fn test_matches_document_basic() {
626 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
628 let contract = fixture.data_contract_owned();
629
630 let internal_clauses = InternalClauses::default();
632 let filter = DriveDocumentQueryFilter {
633 contract: &contract,
634 document_type_name: "niceDocument".to_string(),
635 action_clauses: DocumentActionMatchClauses::Create {
636 new_document_clauses: internal_clauses.clone(),
637 },
638 };
639
640 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
642 id: Identifier::from([3u8; 32]),
643 document_type_name: "niceDocument".to_string(),
644 data_contract_id: contract.id(),
645 identity_contract_nonce: 0,
646 token_payment_info: None,
647 });
648
649 let document_data = BTreeMap::new();
650
651 let id_value: Value = document_base.id().into();
653 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &document_data));
654 }
655
656 #[test]
657 fn test_matches_document_with_primary_key_equal() {
658 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
659 let contract = fixture.data_contract_owned();
660
661 let target_id = Identifier::from([42u8; 32]);
662
663 let internal_clauses = InternalClauses {
664 primary_key_equal_clause: Some(WhereClause {
665 field: "$id".to_string(),
666 operator: WhereOperator::Equal,
667 value: target_id.into(),
668 }),
669 ..Default::default()
670 };
671
672 let filter = DriveDocumentQueryFilter {
673 contract: &contract,
674 document_type_name: "niceDocument".to_string(),
675 action_clauses: DocumentActionMatchClauses::Create {
676 new_document_clauses: internal_clauses.clone(),
677 },
678 };
679
680 let matching_doc = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
682 id: target_id,
683 document_type_name: "niceDocument".to_string(),
684 data_contract_id: contract.id(),
685 identity_contract_nonce: 0,
686 token_payment_info: None,
687 });
688
689 let document_data = BTreeMap::new();
690
691 let id_value: Value = matching_doc.id().into();
692 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &document_data));
693
694 let non_matching_doc = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
696 id: Identifier::from([99u8; 32]),
697 document_type_name: "niceDocument".to_string(),
698 data_contract_id: contract.id(),
699 identity_contract_nonce: 0,
700 token_payment_info: None,
701 });
702
703 let non_id_value: Value = non_matching_doc.id().into();
704 assert!(!filter.evaluate_clauses(&internal_clauses, &non_id_value, &document_data));
705 }
706
707 #[test]
708 fn test_matches_document_with_field_filters() {
709 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
710 let contract = fixture.data_contract_owned();
711
712 let mut equal_clauses = BTreeMap::new();
714 equal_clauses.insert(
715 "name".to_string(),
716 WhereClause {
717 field: "name".to_string(),
718 operator: WhereOperator::Equal,
719 value: Value::Text("example".to_string()),
720 },
721 );
722
723 let internal_clauses = InternalClauses {
724 equal_clauses,
725 ..Default::default()
726 };
727
728 let filter = DriveDocumentQueryFilter {
729 contract: &contract,
730 document_type_name: "niceDocument".to_string(),
731 action_clauses: DocumentActionMatchClauses::Create {
732 new_document_clauses: internal_clauses.clone(),
733 },
734 };
735
736 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
737 id: Identifier::from([3u8; 32]),
738 document_type_name: "niceDocument".to_string(),
739 data_contract_id: contract.id(),
740 identity_contract_nonce: 0,
741 token_payment_info: None,
742 });
743
744 let mut matching_data = BTreeMap::new();
746 matching_data.insert("name".to_string(), Value::Text("example".to_string()));
747
748 let id_value: Value = document_base.id().into();
749 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &matching_data));
750
751 let mut non_matching_data = BTreeMap::new();
753 non_matching_data.insert("name".to_string(), Value::Text("different".to_string()));
754
755 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &non_matching_data));
756
757 let empty_data = BTreeMap::new();
759 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &empty_data));
760 }
761
762 #[test]
763 fn test_matches_document_with_in_operator() {
764 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
765 let contract = fixture.data_contract_owned();
766
767 let allowed_values = vec![
768 Value::Text("active".to_string()),
769 Value::Text("pending".to_string()),
770 ];
771
772 let internal_clauses = InternalClauses {
773 in_clause: Some(WhereClause {
774 field: "status".to_string(),
775 operator: WhereOperator::In,
776 value: Value::Array(allowed_values),
777 }),
778 ..Default::default()
779 };
780
781 let filter = DriveDocumentQueryFilter {
782 contract: &contract,
783 document_type_name: "niceDocument".to_string(),
784 action_clauses: DocumentActionMatchClauses::Create {
785 new_document_clauses: internal_clauses.clone(),
786 },
787 };
788
789 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
790 id: Identifier::from([3u8; 32]),
791 document_type_name: "niceDocument".to_string(),
792 data_contract_id: contract.id(),
793 identity_contract_nonce: 0,
794 token_payment_info: None,
795 });
796
797 let mut matching_data = BTreeMap::new();
799 matching_data.insert("status".to_string(), Value::Text("active".to_string()));
800 let id_value: Value = document_base.id().into();
801 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &matching_data));
802
803 let mut non_matching_data = BTreeMap::new();
805 non_matching_data.insert("status".to_string(), Value::Text("completed".to_string()));
806 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &non_matching_data));
807 }
808
809 #[test]
810 fn test_matches_document_with_range_operators() {
811 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
812 let contract = fixture.data_contract_owned();
813
814 let internal_clauses = InternalClauses {
816 range_clause: Some(WhereClause {
817 field: "score".to_string(),
818 operator: WhereOperator::GreaterThan,
819 value: Value::U64(50),
820 }),
821 ..Default::default()
822 };
823
824 let filter = DriveDocumentQueryFilter {
825 contract: &contract,
826 document_type_name: "niceDocument".to_string(),
827 action_clauses: DocumentActionMatchClauses::Create {
828 new_document_clauses: internal_clauses.clone(),
829 },
830 };
831
832 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
833 id: Identifier::from([3u8; 32]),
834 document_type_name: "niceDocument".to_string(),
835 data_contract_id: contract.id(),
836 identity_contract_nonce: 0,
837 token_payment_info: None,
838 });
839
840 let mut greater_data = BTreeMap::new();
842 greater_data.insert("score".to_string(), Value::U64(75));
843 let id_value: Value = document_base.id().into();
844 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &greater_data));
845
846 let mut equal_data = BTreeMap::new();
848 equal_data.insert("score".to_string(), Value::U64(50));
849 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &equal_data));
850
851 let mut less_data = BTreeMap::new();
853 less_data.insert("score".to_string(), Value::U64(25));
854 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &less_data));
855 }
856
857 #[test]
858 fn test_matches_document_with_nested_field() {
859 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
860 let contract = fixture.data_contract_owned();
861
862 let mut equal_clauses = BTreeMap::new();
864 equal_clauses.insert(
865 "meta.status".to_string(),
866 WhereClause {
867 field: "meta.status".to_string(),
868 operator: WhereOperator::Equal,
869 value: Value::Text("active".to_string()),
870 },
871 );
872
873 let internal_clauses = InternalClauses {
874 equal_clauses,
875 ..Default::default()
876 };
877
878 let filter = DriveDocumentQueryFilter {
879 contract: &contract,
880 document_type_name: "niceDocument".to_string(),
881 action_clauses: DocumentActionMatchClauses::Create {
882 new_document_clauses: internal_clauses.clone(),
883 },
884 };
885
886 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
887 id: Identifier::from([3u8; 32]),
888 document_type_name: "niceDocument".to_string(),
889 data_contract_id: contract.id(),
890 identity_contract_nonce: 0,
891 token_payment_info: None,
892 });
893
894 let nested = vec![(
896 Value::Text("status".to_string()),
897 Value::Text("active".to_string()),
898 )];
899 let mut data = BTreeMap::new();
900 data.insert("meta".to_string(), Value::Map(nested));
901
902 let id_value: Value = document_base.id().into();
903 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &data));
904 }
905
906 #[test]
907 fn test_validate_optional_actions_allow_empty_clauses() {
908 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
909 let contract = fixture.data_contract_owned();
910
911 let filter = DriveDocumentQueryFilter {
913 contract: &contract,
914 document_type_name: "niceDocument".to_string(),
915 action_clauses: DocumentActionMatchClauses::Replace {
916 original_document_clauses: InternalClauses::default(),
917 new_document_clauses: InternalClauses::default(),
918 },
919 };
920 assert!(filter.validate().is_valid());
921
922 let filter = DriveDocumentQueryFilter {
924 contract: &contract,
925 document_type_name: "niceDocument".to_string(),
926 action_clauses: DocumentActionMatchClauses::Replace {
927 original_document_clauses: InternalClauses::default(),
928 new_document_clauses: InternalClauses {
929 primary_key_equal_clause: Some(WhereClause {
930 field: "$id".to_string(),
931 operator: WhereOperator::Equal,
932 value: Value::Identifier([3u8; 32]),
933 }),
934 ..Default::default()
935 },
936 },
937 };
938 assert!(filter.validate().is_valid());
939
940 let filter = DriveDocumentQueryFilter {
942 contract: &contract,
943 document_type_name: "niceDocument".to_string(),
944 action_clauses: DocumentActionMatchClauses::Transfer {
945 original_document_clauses: InternalClauses::default(),
946 owner_clause: None,
947 },
948 };
949 assert!(filter.validate().is_valid());
950
951 let filter = DriveDocumentQueryFilter {
953 contract: &contract,
954 document_type_name: "niceDocument".to_string(),
955 action_clauses: DocumentActionMatchClauses::Transfer {
956 original_document_clauses: InternalClauses::default(),
957 owner_clause: Some(ValueClause {
958 operator: WhereOperator::Equal,
959 value: Value::Identifier([1u8; 32]),
960 }),
961 },
962 };
963 assert!(filter.validate().is_valid());
964
965 let filter = DriveDocumentQueryFilter {
967 contract: &contract,
968 document_type_name: "niceDocument".to_string(),
969 action_clauses: DocumentActionMatchClauses::UpdatePrice {
970 original_document_clauses: InternalClauses::default(),
971 price_clause: None,
972 },
973 };
974 assert!(filter.validate().is_valid());
975
976 let filter = DriveDocumentQueryFilter {
978 contract: &contract,
979 document_type_name: "niceDocument".to_string(),
980 action_clauses: DocumentActionMatchClauses::UpdatePrice {
981 original_document_clauses: InternalClauses::default(),
982 price_clause: Some(ValueClause {
983 operator: WhereOperator::GreaterThan,
984 value: Value::U64(0),
985 }),
986 },
987 };
988 assert!(filter.validate().is_valid());
989
990 let filter = DriveDocumentQueryFilter {
992 contract: &contract,
993 document_type_name: "niceDocument".to_string(),
994 action_clauses: DocumentActionMatchClauses::Purchase {
995 original_document_clauses: InternalClauses::default(),
996 owner_clause: None,
997 },
998 };
999 assert!(filter.validate().is_valid());
1000
1001 let filter = DriveDocumentQueryFilter {
1003 contract: &contract,
1004 document_type_name: "niceDocument".to_string(),
1005 action_clauses: DocumentActionMatchClauses::Purchase {
1006 original_document_clauses: InternalClauses::default(),
1007 owner_clause: Some(ValueClause {
1008 operator: WhereOperator::Equal,
1009 value: Value::Identifier([2u8; 32]),
1010 }),
1011 },
1012 };
1013 assert!(filter.validate().is_valid());
1014 }
1015
1016 #[test]
1017 fn test_transfer_owner_clause_only_matches() {
1018 use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::v0::DocumentTransferTransitionV0;
1019 use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::DocumentTransferTransition;
1020
1021 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1022 let contract = fixture.data_contract_owned();
1023
1024 let new_owner = Identifier::from([5u8; 32]);
1025
1026 let filter = DriveDocumentQueryFilter {
1028 contract: &contract,
1029 document_type_name: "niceDocument".to_string(),
1030 action_clauses: DocumentActionMatchClauses::Transfer {
1031 original_document_clauses: InternalClauses::default(),
1032 owner_clause: Some(ValueClause {
1033 operator: WhereOperator::Equal,
1034 value: new_owner.into(),
1035 }),
1036 },
1037 };
1038
1039 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1041 id: Identifier::from([3u8; 32]),
1042 document_type_name: "niceDocument".to_string(),
1043 data_contract_id: contract.id(),
1044 identity_contract_nonce: 0,
1045 token_payment_info: None,
1046 });
1047
1048 let transfer_v0 = DocumentTransferTransitionV0 {
1049 base: document_base.clone(),
1050 revision: 1_u64,
1051 recipient_owner_id: new_owner,
1052 };
1053 let transfer = DocumentTransition::Transfer(DocumentTransferTransition::V0(transfer_v0));
1054
1055 assert_eq!(
1057 filter.matches_document_transition(&transfer, None),
1058 TransitionCheckResult::Pass
1059 );
1060
1061 let other_owner = Identifier::from([6u8; 32]);
1063 let transfer_v0_mismatch = DocumentTransferTransitionV0 {
1064 base: document_base,
1065 revision: 1_u64,
1066 recipient_owner_id: other_owner,
1067 };
1068 let transfer_mismatch =
1069 DocumentTransition::Transfer(DocumentTransferTransition::V0(transfer_v0_mismatch));
1070 assert_eq!(
1071 filter.matches_document_transition(&transfer_mismatch, None),
1072 TransitionCheckResult::Fail
1073 );
1074 }
1075
1076 #[test]
1077 fn test_purchase_owner_clause_only_matches_and_requires_owner_context() {
1078 use dpp::fee::Credits;
1079 use dpp::state_transition::batch_transition::batched_transition::document_purchase_transition::v0::DocumentPurchaseTransitionV0;
1080 use dpp::state_transition::batch_transition::batched_transition::document_purchase_transition::DocumentPurchaseTransition;
1081
1082 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1083 let contract = fixture.data_contract_owned();
1084
1085 let purchaser = Identifier::from([7u8; 32]);
1086
1087 let filter = DriveDocumentQueryFilter {
1089 contract: &contract,
1090 document_type_name: "niceDocument".to_string(),
1091 action_clauses: DocumentActionMatchClauses::Purchase {
1092 original_document_clauses: InternalClauses::default(),
1093 owner_clause: Some(ValueClause {
1094 operator: WhereOperator::Equal,
1095 value: purchaser.into(),
1096 }),
1097 },
1098 };
1099
1100 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1102 id: Identifier::from([4u8; 32]),
1103 document_type_name: "niceDocument".to_string(),
1104 data_contract_id: contract.id(),
1105 identity_contract_nonce: 0,
1106 token_payment_info: None,
1107 });
1108
1109 let purchase_v0 = DocumentPurchaseTransitionV0 {
1110 base: document_base,
1111 revision: 1_u64,
1112 price: 10 as Credits,
1113 };
1114 let purchase = DocumentTransition::Purchase(DocumentPurchaseTransition::V0(purchase_v0));
1115
1116 assert_eq!(
1118 filter.matches_document_transition(&purchase, None),
1119 TransitionCheckResult::Fail
1120 );
1121 let owner_value = Value::Identifier(purchaser.to_buffer());
1123 assert_eq!(
1124 filter.matches_document_transition(&purchase, Some(&owner_value)),
1125 TransitionCheckResult::Pass
1126 );
1127 }
1128
1129 #[test]
1130 fn test_transfer_original_clause_only_matches_with_original_document() {
1131 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1132 let contract = fixture.data_contract_owned();
1133
1134 let mut eq = BTreeMap::new();
1136 eq.insert(
1137 "status".to_string(),
1138 WhereClause {
1139 field: "status".to_string(),
1140 operator: WhereOperator::Equal,
1141 value: Value::Text("active".to_string()),
1142 },
1143 );
1144 let filter = DriveDocumentQueryFilter {
1145 contract: &contract,
1146 document_type_name: "niceDocument".to_string(),
1147 action_clauses: DocumentActionMatchClauses::Transfer {
1148 original_document_clauses: InternalClauses {
1149 equal_clauses: eq,
1150 ..Default::default()
1151 },
1152 owner_clause: None,
1153 },
1154 };
1155
1156 let mut original = BTreeMap::new();
1158 original.insert("status".to_string(), Value::Text("active".to_string()));
1159 let original_doc = Document::V0(DocumentV0 {
1160 id: Identifier::from([9u8; 32]),
1161 owner_id: Identifier::from([0u8; 32]),
1162 properties: original,
1163 ..Default::default()
1164 });
1165 assert!(filter.matches_original_document(&original_doc));
1166
1167 }
1170
1171 #[test]
1172 fn test_delete_original_clause_only_matches_with_original_document() {
1173 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1174 let contract = fixture.data_contract_owned();
1175
1176 let mut eq = BTreeMap::new();
1178 eq.insert(
1179 "status".to_string(),
1180 WhereClause {
1181 field: "status".to_string(),
1182 operator: WhereOperator::Equal,
1183 value: Value::Text("active".to_string()),
1184 },
1185 );
1186 let filter = DriveDocumentQueryFilter {
1187 contract: &contract,
1188 document_type_name: "niceDocument".to_string(),
1189 action_clauses: DocumentActionMatchClauses::Delete {
1190 original_document_clauses: InternalClauses {
1191 equal_clauses: eq,
1192 ..Default::default()
1193 },
1194 },
1195 };
1196
1197 let mut original = BTreeMap::new();
1199 original.insert("status".to_string(), Value::Text("active".to_string()));
1200 let original_doc = Document::V0(DocumentV0 {
1201 id: Identifier::from([12u8; 32]),
1202 owner_id: Identifier::from([0u8; 32]),
1203 properties: original,
1204 ..Default::default()
1205 });
1206 assert!(filter.matches_original_document(&original_doc));
1207
1208 let mut original_bad = BTreeMap::new();
1213 original_bad.insert("status".to_string(), Value::Text("inactive".to_string()));
1214 let original_doc_bad = Document::V0(DocumentV0 {
1215 id: Identifier::from([12u8; 32]),
1216 owner_id: Identifier::from([0u8; 32]),
1217 properties: original_bad,
1218 ..Default::default()
1219 });
1220 assert!(!filter.matches_original_document(&original_doc_bad));
1221 }
1222
1223 #[test]
1224 fn test_update_price_price_clause_only_matches_and_with_original_clause() {
1225 use dpp::fee::Credits;
1226 use dpp::state_transition::batch_transition::batched_transition::document_update_price_transition::v0::DocumentUpdatePriceTransitionV0;
1227 use dpp::state_transition::batch_transition::batched_transition::document_update_price_transition::DocumentUpdatePriceTransition;
1228
1229 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1230 let contract = fixture.data_contract_owned();
1231
1232 let base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1233 id: Identifier::from([10u8; 32]),
1234 document_type_name: "niceDocument".to_string(),
1235 data_contract_id: contract.id(),
1236 identity_contract_nonce: 0,
1237 token_payment_info: None,
1238 });
1239
1240 let update_v0 = DocumentUpdatePriceTransitionV0 {
1242 base: base.clone(),
1243 revision: 1,
1244 price: 10 as Credits,
1245 };
1246 let update = DocumentTransition::UpdatePrice(DocumentUpdatePriceTransition::V0(update_v0));
1247
1248 let filter_price_only = DriveDocumentQueryFilter {
1249 contract: &contract,
1250 document_type_name: "niceDocument".to_string(),
1251 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1252 original_document_clauses: InternalClauses::default(),
1253 price_clause: Some(ValueClause {
1254 operator: WhereOperator::GreaterThan,
1255 value: Value::U64(5),
1256 }),
1257 },
1258 };
1259 assert_eq!(
1261 filter_price_only.matches_document_transition(&update, None),
1262 TransitionCheckResult::Pass
1263 );
1264
1265 let filter_price_only_fail = DriveDocumentQueryFilter {
1266 contract: &contract,
1267 document_type_name: "niceDocument".to_string(),
1268 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1269 original_document_clauses: InternalClauses::default(),
1270 price_clause: Some(ValueClause {
1271 operator: WhereOperator::GreaterThan,
1272 value: Value::U64(15),
1273 }),
1274 },
1275 };
1276 assert_eq!(
1277 filter_price_only_fail.matches_document_transition(&update, None),
1278 TransitionCheckResult::Fail
1279 );
1280
1281 let mut eq = BTreeMap::new();
1283 eq.insert(
1284 "kind".to_string(),
1285 WhereClause {
1286 field: "kind".to_string(),
1287 operator: WhereOperator::Equal,
1288 value: Value::Text("sale".to_string()),
1289 },
1290 );
1291 let filter_with_orig = DriveDocumentQueryFilter {
1292 contract: &contract,
1293 document_type_name: "niceDocument".to_string(),
1294 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1295 original_document_clauses: InternalClauses {
1296 equal_clauses: eq,
1297 ..Default::default()
1298 },
1299 price_clause: Some(ValueClause {
1300 operator: WhereOperator::GreaterThanOrEquals,
1301 value: Value::U64(10),
1302 }),
1303 },
1304 };
1305 let mut original_doc = BTreeMap::new();
1306 original_doc.insert("kind".to_string(), Value::Text("sale".to_string()));
1307 assert_eq!(
1308 filter_with_orig.matches_document_transition(&update, None),
1309 TransitionCheckResult::NeedsOriginal
1310 );
1311 let original_document = Document::V0(DocumentV0 {
1312 id: Identifier::from([10u8; 32]),
1313 owner_id: Identifier::from([0u8; 32]),
1314 properties: original_doc,
1315 ..Default::default()
1316 });
1317 assert!(filter_with_orig.matches_original_document(&original_document));
1318
1319 }
1322
1323 #[test]
1324 fn test_replace_with_both_original_and_new_document_clauses() {
1325 use dpp::state_transition::batch_transition::batched_transition::document_replace_transition::v0::DocumentReplaceTransitionV0;
1326 use dpp::state_transition::batch_transition::batched_transition::document_replace_transition::DocumentReplaceTransition;
1327
1328 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1329 let contract = fixture.data_contract_owned();
1330
1331 let base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1332 id: Identifier::from([11u8; 32]),
1333 document_type_name: "niceDocument".to_string(),
1334 data_contract_id: contract.id(),
1335 identity_contract_nonce: 0,
1336 token_payment_info: None,
1337 });
1338
1339 let mut orig_eq = BTreeMap::new();
1341 orig_eq.insert(
1342 "status".to_string(),
1343 WhereClause {
1344 field: "status".to_string(),
1345 operator: WhereOperator::Equal,
1346 value: Value::Text("active".to_string()),
1347 },
1348 );
1349 let original_clauses = InternalClauses {
1350 equal_clauses: orig_eq,
1351 ..Default::default()
1352 };
1353
1354 let mut final_eq = BTreeMap::new();
1355 final_eq.insert(
1356 "score".to_string(),
1357 WhereClause {
1358 field: "score".to_string(),
1359 operator: WhereOperator::Equal,
1360 value: Value::U64(10),
1361 },
1362 );
1363 let new_document_clauses = InternalClauses {
1364 equal_clauses: final_eq,
1365 ..Default::default()
1366 };
1367
1368 let filter = DriveDocumentQueryFilter {
1369 contract: &contract,
1370 document_type_name: "niceDocument".to_string(),
1371 action_clauses: DocumentActionMatchClauses::Replace {
1372 original_document_clauses: original_clauses,
1373 new_document_clauses,
1374 },
1375 };
1376
1377 let mut data = BTreeMap::new();
1379 data.insert("score".to_string(), Value::U64(10));
1380 let replace_v0 = DocumentReplaceTransitionV0 {
1381 base,
1382 revision: 1,
1383 data,
1384 };
1385 let replace = DocumentTransition::Replace(DocumentReplaceTransition::V0(replace_v0));
1386
1387 let mut original_doc = BTreeMap::new();
1389 original_doc.insert("status".to_string(), Value::Text("active".to_string()));
1390 assert_eq!(
1391 filter.matches_document_transition(&replace, None),
1392 TransitionCheckResult::NeedsOriginal
1393 );
1394 let original_document = Document::V0(DocumentV0 {
1395 id: Identifier::from([11u8; 32]),
1396 owner_id: Identifier::from([0u8; 32]),
1397 properties: original_doc,
1398 ..Default::default()
1399 });
1400 assert!(filter.matches_original_document(&original_document));
1401
1402 let mut original_doc_bad = BTreeMap::new();
1407 original_doc_bad.insert("status".to_string(), Value::Text("inactive".to_string()));
1408 let original_document_bad = Document::V0(DocumentV0 {
1409 id: Identifier::from([11u8; 32]),
1410 owner_id: Identifier::from([0u8; 32]),
1411 properties: original_doc_bad,
1412 ..Default::default()
1413 });
1414 assert!(!filter.matches_original_document(&original_document_bad));
1415
1416 if let DocumentTransition::Replace(mut rep) = replace.clone() {
1418 let DocumentReplaceTransition::V0(ref mut v0) = rep;
1419 v0.data.insert("score".to_string(), Value::U64(9));
1420 let bad_final = DocumentTransition::Replace(rep);
1421 assert_eq!(
1422 filter.matches_document_transition(&bad_final, None),
1423 TransitionCheckResult::Fail
1424 );
1425 }
1426 }
1427
1428 #[test]
1429 fn test_matches_document_with_between_operator() {
1430 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1431 let contract = fixture.data_contract_owned();
1432
1433 let internal_clauses = InternalClauses {
1434 range_clause: Some(WhereClause {
1435 field: "value".to_string(),
1436 operator: WhereOperator::Between,
1437 value: Value::Array(vec![Value::U64(10), Value::U64(20)]),
1438 }),
1439 ..Default::default()
1440 };
1441
1442 let filter = DriveDocumentQueryFilter {
1443 contract: &contract,
1444 document_type_name: "niceDocument".to_string(),
1445 action_clauses: DocumentActionMatchClauses::Create {
1446 new_document_clauses: internal_clauses.clone(),
1447 },
1448 };
1449
1450 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1451 id: Identifier::from([3u8; 32]),
1452 document_type_name: "niceDocument".to_string(),
1453 data_contract_id: contract.id(),
1454 identity_contract_nonce: 0,
1455 token_payment_info: None,
1456 });
1457
1458 let mut in_range = BTreeMap::new();
1460 in_range.insert("value".to_string(), Value::U64(15));
1461 let id_value: Value = document_base.id().into();
1462 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &in_range));
1463
1464 let mut lower_bound = BTreeMap::new();
1466 lower_bound.insert("value".to_string(), Value::U64(10));
1467 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &lower_bound));
1468
1469 let mut upper_bound = BTreeMap::new();
1471 upper_bound.insert("value".to_string(), Value::U64(20));
1472 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &upper_bound));
1473
1474 let mut below = BTreeMap::new();
1476 below.insert("value".to_string(), Value::U64(5));
1477 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &below));
1478
1479 let mut above = BTreeMap::new();
1481 above.insert("value".to_string(), Value::U64(25));
1482 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &above));
1483 }
1484
1485 #[test]
1486 fn test_validate_filter() {
1487 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1488 let contract = fixture.data_contract_owned();
1489
1490 let mut equal_clauses = BTreeMap::new();
1492 equal_clauses.insert(
1493 "firstName".to_string(),
1494 WhereClause {
1495 field: "firstName".to_string(),
1496 operator: WhereOperator::Equal,
1497 value: Value::Text("Alice".to_string()),
1498 },
1499 );
1500 let internal_clauses = InternalClauses {
1501 equal_clauses,
1502 ..Default::default()
1503 };
1504
1505 let valid_filter = DriveDocumentQueryFilter {
1506 contract: &contract,
1507 document_type_name: "indexedDocument".to_string(),
1508 action_clauses: DocumentActionMatchClauses::Create {
1509 new_document_clauses: internal_clauses,
1510 },
1511 };
1512
1513 assert!(
1514 valid_filter.validate().is_valid(),
1515 "Filter with indexed field should be valid"
1516 );
1517
1518 let mut equal_clauses = BTreeMap::new();
1521 equal_clauses.insert(
1522 "name".to_string(),
1523 WhereClause {
1524 field: "name".to_string(),
1525 operator: WhereOperator::Equal,
1526 value: Value::Text("value".to_string()),
1527 },
1528 );
1529 let internal_clauses = InternalClauses {
1530 equal_clauses,
1531 ..Default::default()
1532 };
1533
1534 let invalid_filter = DriveDocumentQueryFilter {
1535 contract: &contract,
1536 document_type_name: "niceDocument".to_string(),
1537 action_clauses: DocumentActionMatchClauses::Create {
1538 new_document_clauses: internal_clauses,
1539 },
1540 };
1541
1542 assert!(
1543 invalid_filter.validate().is_valid(),
1544 "Structural validate should ignore indexes"
1545 );
1546 let internal_clauses = InternalClauses {
1550 primary_key_equal_clause: Some(WhereClause {
1551 field: "$id".to_string(),
1552 operator: WhereOperator::Equal,
1553 value: Value::Identifier([42u8; 32]),
1554 }),
1555 ..Default::default()
1556 };
1557
1558 let primary_key_filter = DriveDocumentQueryFilter {
1559 contract: &contract,
1560 document_type_name: "indexedDocument".to_string(),
1561 action_clauses: DocumentActionMatchClauses::Create {
1562 new_document_clauses: internal_clauses,
1563 },
1564 };
1565
1566 assert!(
1567 primary_key_filter.validate().is_valid(),
1568 "Filter with only primary key should be valid"
1569 );
1570 }
1571
1572 #[test]
1573 fn test_validate_rejects_id_in_generic_clauses() {
1574 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1575 let contract = fixture.data_contract_owned();
1576
1577 let mut eq = BTreeMap::new();
1579 eq.insert(
1580 "$id".to_string(),
1581 WhereClause {
1582 field: "$id".to_string(),
1583 operator: WhereOperator::Equal,
1584 value: Value::Identifier([1u8; 32]),
1585 },
1586 );
1587 let filter = DriveDocumentQueryFilter {
1588 contract: &contract,
1589 document_type_name: "niceDocument".to_string(),
1590 action_clauses: DocumentActionMatchClauses::Create {
1591 new_document_clauses: InternalClauses {
1592 equal_clauses: eq,
1593 ..Default::default()
1594 },
1595 },
1596 };
1597 assert!(filter.validate().is_err());
1598
1599 let filter = DriveDocumentQueryFilter {
1601 contract: &contract,
1602 document_type_name: "niceDocument".to_string(),
1603 action_clauses: DocumentActionMatchClauses::Create {
1604 new_document_clauses: InternalClauses {
1605 range_clause: Some(WhereClause {
1606 field: "$id".to_string(),
1607 operator: WhereOperator::GreaterThan,
1608 value: Value::U64(0),
1609 }),
1610 ..Default::default()
1611 },
1612 },
1613 };
1614 assert!(filter.validate().is_err());
1615 }
1616
1617 #[test]
1618 fn test_validate_owner_and_price_clause_types() {
1619 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1620 let contract = fixture.data_contract_owned();
1621
1622 let filter = DriveDocumentQueryFilter {
1624 contract: &contract,
1625 document_type_name: "niceDocument".to_string(),
1626 action_clauses: DocumentActionMatchClauses::Transfer {
1627 original_document_clauses: InternalClauses::default(),
1628 owner_clause: Some(ValueClause {
1629 operator: WhereOperator::Equal,
1630 value: Value::Text("not-id".to_string()),
1631 }),
1632 },
1633 };
1634 assert!(filter.validate().is_err());
1635
1636 let filter = DriveDocumentQueryFilter {
1638 contract: &contract,
1639 document_type_name: "niceDocument".to_string(),
1640 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1641 original_document_clauses: InternalClauses::default(),
1642 price_clause: Some(ValueClause {
1643 operator: WhereOperator::Equal,
1644 value: Value::Float(1.23),
1645 }),
1646 },
1647 };
1648 assert!(filter.validate().is_err());
1649
1650 let filter = DriveDocumentQueryFilter {
1652 contract: &contract,
1653 document_type_name: "niceDocument".to_string(),
1654 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1655 original_document_clauses: InternalClauses::default(),
1656 price_clause: Some(ValueClause {
1657 operator: WhereOperator::Between,
1658 value: Value::Array(vec![Value::U64(1), Value::Float(2.0)]),
1659 }),
1660 },
1661 };
1662 assert!(filter.validate().is_err());
1663
1664 for operator in [
1666 WhereOperator::Between,
1667 WhereOperator::BetweenExcludeBounds,
1668 WhereOperator::BetweenExcludeLeft,
1669 WhereOperator::BetweenExcludeRight,
1670 ] {
1671 let filter = DriveDocumentQueryFilter {
1672 contract: &contract,
1673 document_type_name: "niceDocument".to_string(),
1674 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1675 original_document_clauses: InternalClauses::default(),
1676 price_clause: Some(ValueClause {
1677 operator,
1678 value: Value::Array(vec![Value::U64(10), Value::U64(10)]),
1679 }),
1680 },
1681 };
1682 assert!(
1683 filter.validate().is_err(),
1684 "{operator:?} should reject equal price bounds"
1685 );
1686 }
1687 }
1688
1689 #[test]
1690 fn test_validate_startswith_on_numeric_field_rejected() {
1691 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1692 let contract = fixture.data_contract_owned();
1693
1694 let filter = DriveDocumentQueryFilter {
1696 contract: &contract,
1697 document_type_name: "niceDocument".to_string(),
1698 action_clauses: DocumentActionMatchClauses::Create {
1699 new_document_clauses: InternalClauses {
1700 range_clause: Some(WhereClause {
1701 field: "score".to_string(),
1702 operator: WhereOperator::StartsWith,
1703 value: Value::Text("1".to_string()),
1704 }),
1705 ..Default::default()
1706 },
1707 },
1708 };
1709 assert!(filter.validate().is_err());
1710 }
1711
1712 #[test]
1713 fn test_conversion_between_filter_and_query() {
1714 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1715 let contract = fixture.data_contract_owned();
1716
1717 let internal_clauses = InternalClauses {
1718 primary_key_equal_clause: Some(WhereClause {
1719 field: "$id".to_string(),
1720 operator: WhereOperator::Equal,
1721 value: Value::Identifier([42u8; 32]),
1722 }),
1723 ..Default::default()
1724 };
1725
1726 let original_filter = DriveDocumentQueryFilter {
1727 contract: &contract,
1728 document_type_name: "niceDocument".to_string(),
1729 action_clauses: DocumentActionMatchClauses::Create {
1730 new_document_clauses: internal_clauses.clone(),
1731 },
1732 };
1733
1734 if let DocumentActionMatchClauses::Create {
1736 new_document_clauses,
1737 } = original_filter.action_clauses
1738 {
1739 assert_eq!(new_document_clauses, internal_clauses);
1740 } else {
1741 panic!("expected Create action clauses");
1742 }
1743 }
1744
1745 #[test]
1748 fn validate_rejects_unknown_document_type() {
1749 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1750 let contract = fixture.data_contract_owned();
1751
1752 let filter = DriveDocumentQueryFilter {
1753 contract: &contract,
1754 document_type_name: "doesNotExist".to_string(),
1755 action_clauses: DocumentActionMatchClauses::Create {
1756 new_document_clauses: InternalClauses::default(),
1757 },
1758 };
1759 let result = filter.validate();
1760 assert!(result.is_err());
1761 assert!(matches!(
1762 result.first_error(),
1763 Some(QuerySyntaxError::DocumentTypeNotFound(_))
1764 ));
1765 }
1766
1767 #[test]
1770 fn validate_transfer_owner_clause_in_with_identifiers_is_valid() {
1771 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1772 let contract = fixture.data_contract_owned();
1773
1774 let filter = DriveDocumentQueryFilter {
1775 contract: &contract,
1776 document_type_name: "niceDocument".to_string(),
1777 action_clauses: DocumentActionMatchClauses::Transfer {
1778 original_document_clauses: InternalClauses::default(),
1779 owner_clause: Some(ValueClause {
1780 operator: WhereOperator::In,
1781 value: Value::Array(vec![
1782 Value::Identifier([1u8; 32]),
1783 Value::Identifier([2u8; 32]),
1784 ]),
1785 }),
1786 },
1787 };
1788 assert!(filter.validate().is_valid());
1789 }
1790
1791 #[test]
1792 fn validate_transfer_owner_clause_in_with_non_identifiers_is_invalid() {
1793 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1794 let contract = fixture.data_contract_owned();
1795
1796 let filter = DriveDocumentQueryFilter {
1797 contract: &contract,
1798 document_type_name: "niceDocument".to_string(),
1799 action_clauses: DocumentActionMatchClauses::Transfer {
1800 original_document_clauses: InternalClauses::default(),
1801 owner_clause: Some(ValueClause {
1802 operator: WhereOperator::In,
1803 value: Value::Array(vec![Value::Text("not-an-id".to_string())]),
1804 }),
1805 },
1806 };
1807 assert!(filter.validate().is_err());
1808 }
1809
1810 #[test]
1811 fn validate_transfer_owner_clause_greater_than_is_invalid() {
1812 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1813 let contract = fixture.data_contract_owned();
1814
1815 let filter = DriveDocumentQueryFilter {
1816 contract: &contract,
1817 document_type_name: "niceDocument".to_string(),
1818 action_clauses: DocumentActionMatchClauses::Transfer {
1819 original_document_clauses: InternalClauses::default(),
1820 owner_clause: Some(ValueClause {
1821 operator: WhereOperator::GreaterThan,
1822 value: Value::Identifier([1u8; 32]),
1823 }),
1824 },
1825 };
1826 assert!(filter.validate().is_err());
1827 }
1828
1829 #[test]
1832 fn validate_purchase_owner_clause_in_with_identifiers_is_valid() {
1833 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1834 let contract = fixture.data_contract_owned();
1835
1836 let filter = DriveDocumentQueryFilter {
1837 contract: &contract,
1838 document_type_name: "niceDocument".to_string(),
1839 action_clauses: DocumentActionMatchClauses::Purchase {
1840 original_document_clauses: InternalClauses::default(),
1841 owner_clause: Some(ValueClause {
1842 operator: WhereOperator::In,
1843 value: Value::Array(vec![
1844 Value::Identifier([3u8; 32]),
1845 Value::Identifier([4u8; 32]),
1846 ]),
1847 }),
1848 },
1849 };
1850 assert!(filter.validate().is_valid());
1851 }
1852
1853 #[test]
1854 fn validate_purchase_owner_clause_non_identifier_is_invalid() {
1855 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1856 let contract = fixture.data_contract_owned();
1857
1858 let filter = DriveDocumentQueryFilter {
1859 contract: &contract,
1860 document_type_name: "niceDocument".to_string(),
1861 action_clauses: DocumentActionMatchClauses::Purchase {
1862 original_document_clauses: InternalClauses::default(),
1863 owner_clause: Some(ValueClause {
1864 operator: WhereOperator::Equal,
1865 value: Value::U64(42),
1866 }),
1867 },
1868 };
1869 assert!(filter.validate().is_err());
1870 }
1871
1872 #[test]
1873 fn validate_purchase_owner_clause_in_with_non_array_is_invalid() {
1874 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1875 let contract = fixture.data_contract_owned();
1876
1877 let filter = DriveDocumentQueryFilter {
1878 contract: &contract,
1879 document_type_name: "niceDocument".to_string(),
1880 action_clauses: DocumentActionMatchClauses::Purchase {
1881 original_document_clauses: InternalClauses::default(),
1882 owner_clause: Some(ValueClause {
1883 operator: WhereOperator::In,
1884 value: Value::Identifier([1u8; 32]),
1885 }),
1886 },
1887 };
1888 assert!(filter.validate().is_err());
1889 }
1890
1891 #[test]
1894 fn validate_price_clause_starts_with_is_invalid() {
1895 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1896 let contract = fixture.data_contract_owned();
1897
1898 let filter = DriveDocumentQueryFilter {
1899 contract: &contract,
1900 document_type_name: "niceDocument".to_string(),
1901 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1902 original_document_clauses: InternalClauses::default(),
1903 price_clause: Some(ValueClause {
1904 operator: WhereOperator::StartsWith,
1905 value: Value::Text("1".to_string()),
1906 }),
1907 },
1908 };
1909 assert!(filter.validate().is_err());
1910 }
1911
1912 #[test]
1913 fn validate_price_clause_in_with_integers_is_valid() {
1914 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1915 let contract = fixture.data_contract_owned();
1916
1917 let filter = DriveDocumentQueryFilter {
1918 contract: &contract,
1919 document_type_name: "niceDocument".to_string(),
1920 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1921 original_document_clauses: InternalClauses::default(),
1922 price_clause: Some(ValueClause {
1923 operator: WhereOperator::In,
1924 value: Value::Array(vec![Value::U64(10), Value::U64(20), Value::U64(30)]),
1925 }),
1926 },
1927 };
1928 assert!(filter.validate().is_valid());
1929 }
1930
1931 #[test]
1932 fn validate_price_clause_in_with_non_integer_is_invalid() {
1933 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1934 let contract = fixture.data_contract_owned();
1935
1936 let filter = DriveDocumentQueryFilter {
1937 contract: &contract,
1938 document_type_name: "niceDocument".to_string(),
1939 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1940 original_document_clauses: InternalClauses::default(),
1941 price_clause: Some(ValueClause {
1942 operator: WhereOperator::In,
1943 value: Value::Array(vec![Value::Text("not_int".to_string())]),
1944 }),
1945 },
1946 };
1947 assert!(filter.validate().is_err());
1948 }
1949
1950 #[test]
1951 fn validate_price_clause_in_with_non_array_is_invalid() {
1952 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1953 let contract = fixture.data_contract_owned();
1954
1955 let filter = DriveDocumentQueryFilter {
1956 contract: &contract,
1957 document_type_name: "niceDocument".to_string(),
1958 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1959 original_document_clauses: InternalClauses::default(),
1960 price_clause: Some(ValueClause {
1961 operator: WhereOperator::In,
1962 value: Value::U64(10),
1963 }),
1964 },
1965 };
1966 assert!(filter.validate().is_err());
1967 }
1968
1969 #[test]
1970 fn validate_price_clause_between_with_valid_integers_is_valid() {
1971 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1972 let contract = fixture.data_contract_owned();
1973
1974 let filter = DriveDocumentQueryFilter {
1975 contract: &contract,
1976 document_type_name: "niceDocument".to_string(),
1977 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1978 original_document_clauses: InternalClauses::default(),
1979 price_clause: Some(ValueClause {
1980 operator: WhereOperator::Between,
1981 value: Value::Array(vec![Value::U64(10), Value::U64(100)]),
1982 }),
1983 },
1984 };
1985 assert!(filter.validate().is_valid());
1986 }
1987
1988 #[test]
1989 fn validate_price_clause_between_with_descending_bounds_is_invalid() {
1990 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1991 let contract = fixture.data_contract_owned();
1992
1993 let filter = DriveDocumentQueryFilter {
1994 contract: &contract,
1995 document_type_name: "niceDocument".to_string(),
1996 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1997 original_document_clauses: InternalClauses::default(),
1998 price_clause: Some(ValueClause {
1999 operator: WhereOperator::Between,
2000 value: Value::Array(vec![Value::U64(100), Value::U64(10)]),
2001 }),
2002 },
2003 };
2004 assert!(filter.validate().is_err());
2005 }
2006
2007 #[test]
2008 fn validate_price_clause_between_with_non_array_is_invalid() {
2009 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2010 let contract = fixture.data_contract_owned();
2011
2012 let filter = DriveDocumentQueryFilter {
2013 contract: &contract,
2014 document_type_name: "niceDocument".to_string(),
2015 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2016 original_document_clauses: InternalClauses::default(),
2017 price_clause: Some(ValueClause {
2018 operator: WhereOperator::Between,
2019 value: Value::U64(50),
2020 }),
2021 },
2022 };
2023 assert!(filter.validate().is_err());
2024 }
2025
2026 #[test]
2027 fn validate_price_clause_less_than_with_integer_is_valid() {
2028 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2029 let contract = fixture.data_contract_owned();
2030
2031 let filter = DriveDocumentQueryFilter {
2032 contract: &contract,
2033 document_type_name: "niceDocument".to_string(),
2034 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2035 original_document_clauses: InternalClauses::default(),
2036 price_clause: Some(ValueClause {
2037 operator: WhereOperator::LessThan,
2038 value: Value::U64(1000),
2039 }),
2040 },
2041 };
2042 assert!(filter.validate().is_valid());
2043 }
2044
2045 #[test]
2046 fn validate_price_clause_less_than_or_equals_with_integer_is_valid() {
2047 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2048 let contract = fixture.data_contract_owned();
2049
2050 let filter = DriveDocumentQueryFilter {
2051 contract: &contract,
2052 document_type_name: "niceDocument".to_string(),
2053 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2054 original_document_clauses: InternalClauses::default(),
2055 price_clause: Some(ValueClause {
2056 operator: WhereOperator::LessThanOrEquals,
2057 value: Value::U64(500),
2058 }),
2059 },
2060 };
2061 assert!(filter.validate().is_valid());
2062 }
2063
2064 #[test]
2065 fn validate_price_clause_greater_than_or_equals_with_integer_is_valid() {
2066 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2067 let contract = fixture.data_contract_owned();
2068
2069 let filter = DriveDocumentQueryFilter {
2070 contract: &contract,
2071 document_type_name: "niceDocument".to_string(),
2072 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2073 original_document_clauses: InternalClauses::default(),
2074 price_clause: Some(ValueClause {
2075 operator: WhereOperator::GreaterThanOrEquals,
2076 value: Value::U64(50),
2077 }),
2078 },
2079 };
2080 assert!(filter.validate().is_valid());
2081 }
2082
2083 #[test]
2086 fn validate_delete_with_empty_clauses_is_valid() {
2087 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2088 let contract = fixture.data_contract_owned();
2089
2090 let filter = DriveDocumentQueryFilter {
2091 contract: &contract,
2092 document_type_name: "niceDocument".to_string(),
2093 action_clauses: DocumentActionMatchClauses::Delete {
2094 original_document_clauses: InternalClauses::default(),
2095 },
2096 };
2097 assert!(filter.validate().is_valid());
2098 }
2099
2100 #[test]
2101 fn validate_delete_with_primary_key_clause_is_valid() {
2102 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2103 let contract = fixture.data_contract_owned();
2104
2105 let filter = DriveDocumentQueryFilter {
2106 contract: &contract,
2107 document_type_name: "niceDocument".to_string(),
2108 action_clauses: DocumentActionMatchClauses::Delete {
2109 original_document_clauses: InternalClauses {
2110 primary_key_equal_clause: Some(WhereClause {
2111 field: "$id".to_string(),
2112 operator: WhereOperator::Equal,
2113 value: Value::Identifier([99u8; 32]),
2114 }),
2115 ..Default::default()
2116 },
2117 },
2118 };
2119 assert!(filter.validate().is_valid());
2120 }
2121
2122 #[test]
2125 fn matches_original_document_returns_false_for_create_action() {
2126 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2127 let contract = fixture.data_contract_owned();
2128
2129 let filter = DriveDocumentQueryFilter {
2130 contract: &contract,
2131 document_type_name: "niceDocument".to_string(),
2132 action_clauses: DocumentActionMatchClauses::Create {
2133 new_document_clauses: InternalClauses::default(),
2134 },
2135 };
2136
2137 let doc = Document::V0(DocumentV0 {
2138 id: Identifier::from([1u8; 32]),
2139 owner_id: Identifier::from([0u8; 32]),
2140 properties: BTreeMap::new(),
2141 ..Default::default()
2142 });
2143 assert!(!filter.matches_original_document(&doc));
2145 }
2146
2147 #[test]
2150 fn evaluate_clauses_primary_key_in_clause_matches() {
2151 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2152 let contract = fixture.data_contract_owned();
2153
2154 let target_id_1 = Identifier::from([10u8; 32]);
2155 let target_id_2 = Identifier::from([20u8; 32]);
2156
2157 let internal_clauses = InternalClauses {
2158 primary_key_in_clause: Some(WhereClause {
2159 field: "$id".to_string(),
2160 operator: WhereOperator::In,
2161 value: Value::Array(vec![target_id_1.into(), target_id_2.into()]),
2162 }),
2163 ..Default::default()
2164 };
2165
2166 let filter = DriveDocumentQueryFilter {
2167 contract: &contract,
2168 document_type_name: "niceDocument".to_string(),
2169 action_clauses: DocumentActionMatchClauses::Create {
2170 new_document_clauses: internal_clauses.clone(),
2171 },
2172 };
2173
2174 let id_value: Value = target_id_1.into();
2176 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &BTreeMap::new()));
2177
2178 let other_id: Value = Identifier::from([99u8; 32]).into();
2180 assert!(!filter.evaluate_clauses(&internal_clauses, &other_id, &BTreeMap::new()));
2181 }
2182
2183 #[test]
2186 fn evaluate_clauses_primary_key_plus_equal_clause() {
2187 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2188 let contract = fixture.data_contract_owned();
2189
2190 let target_id = Identifier::from([50u8; 32]);
2191
2192 let mut equal_clauses = BTreeMap::new();
2193 equal_clauses.insert(
2194 "name".to_string(),
2195 WhereClause {
2196 field: "name".to_string(),
2197 operator: WhereOperator::Equal,
2198 value: Value::Text("test".to_string()),
2199 },
2200 );
2201
2202 let internal_clauses = InternalClauses {
2203 primary_key_equal_clause: Some(WhereClause {
2204 field: "$id".to_string(),
2205 operator: WhereOperator::Equal,
2206 value: target_id.into(),
2207 }),
2208 equal_clauses,
2209 ..Default::default()
2210 };
2211
2212 let filter = DriveDocumentQueryFilter {
2213 contract: &contract,
2214 document_type_name: "niceDocument".to_string(),
2215 action_clauses: DocumentActionMatchClauses::Create {
2216 new_document_clauses: internal_clauses.clone(),
2217 },
2218 };
2219
2220 let id_value: Value = target_id.into();
2222 let mut data = BTreeMap::new();
2223 data.insert("name".to_string(), Value::Text("test".to_string()));
2224 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &data));
2225
2226 let mut bad_data = BTreeMap::new();
2228 bad_data.insert("name".to_string(), Value::Text("other".to_string()));
2229 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &bad_data));
2230
2231 let wrong_id: Value = Identifier::from([99u8; 32]).into();
2233 assert!(!filter.evaluate_clauses(&internal_clauses, &wrong_id, &data));
2234 }
2235
2236 #[test]
2239 fn get_value_by_path_simple_key() {
2240 let mut root = BTreeMap::new();
2241 root.insert("name".to_string(), Value::Text("alice".to_string()));
2242 let result = get_value_by_path(&root, "name");
2243 assert_eq!(result, Some(&Value::Text("alice".to_string())));
2244 }
2245
2246 #[test]
2247 fn get_value_by_path_missing_key_returns_none() {
2248 let root = BTreeMap::new();
2249 let result = get_value_by_path(&root, "nonexistent");
2250 assert!(result.is_none());
2251 }
2252
2253 #[test]
2254 fn get_value_by_path_empty_path_returns_none() {
2255 let mut root = BTreeMap::new();
2256 root.insert("x".to_string(), Value::I64(1));
2257 let result = get_value_by_path(&root, "");
2258 assert!(result.is_none());
2259 }
2260
2261 #[test]
2262 fn get_value_by_path_nested_map() {
2263 let nested = vec![(
2264 Value::Text("level2".to_string()),
2265 Value::Text("deep_value".to_string()),
2266 )];
2267 let mut root = BTreeMap::new();
2268 root.insert("level1".to_string(), Value::Map(nested));
2269
2270 let result = get_value_by_path(&root, "level1.level2");
2271 assert_eq!(result, Some(&Value::Text("deep_value".to_string())));
2272 }
2273
2274 #[test]
2275 fn get_value_by_path_intermediate_non_map_returns_none() {
2276 let mut root = BTreeMap::new();
2277 root.insert("scalar".to_string(), Value::I64(42));
2278
2279 let result = get_value_by_path(&root, "scalar.child");
2281 assert!(result.is_none());
2282 }
2283
2284 #[test]
2285 fn get_value_by_path_deeply_nested() {
2286 let level3 = vec![(Value::Text("val".to_string()), Value::U64(999))];
2287 let level2 = vec![(Value::Text("c".to_string()), Value::Map(level3))];
2288 let mut root = BTreeMap::new();
2289 root.insert("a".to_string(), Value::Map(level2));
2290
2291 let result = get_value_by_path(&root, "a.c.val");
2292 assert_eq!(result, Some(&Value::U64(999)));
2293 }
2294
2295 #[test]
2296 fn get_value_by_path_missing_intermediate_key_returns_none() {
2297 let nested = vec![(Value::Text("exists".to_string()), Value::I64(1))];
2298 let mut root = BTreeMap::new();
2299 root.insert("a".to_string(), Value::Map(nested));
2300
2301 let result = get_value_by_path(&root, "a.not_here.val");
2302 assert!(result.is_none());
2303 }
2304
2305 #[test]
2308 fn evaluate_clauses_range_clause_missing_field_returns_false() {
2309 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2310 let contract = fixture.data_contract_owned();
2311
2312 let internal_clauses = InternalClauses {
2313 range_clause: Some(WhereClause {
2314 field: "nonexistent".to_string(),
2315 operator: WhereOperator::GreaterThan,
2316 value: Value::U64(0),
2317 }),
2318 ..Default::default()
2319 };
2320
2321 let filter = DriveDocumentQueryFilter {
2322 contract: &contract,
2323 document_type_name: "niceDocument".to_string(),
2324 action_clauses: DocumentActionMatchClauses::Create {
2325 new_document_clauses: internal_clauses.clone(),
2326 },
2327 };
2328
2329 let id_value: Value = Identifier::from([1u8; 32]).into();
2330 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &BTreeMap::new()));
2331 }
2332
2333 #[test]
2336 fn evaluate_clauses_in_clause_missing_field_returns_false() {
2337 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2338 let contract = fixture.data_contract_owned();
2339
2340 let internal_clauses = InternalClauses {
2341 in_clause: Some(WhereClause {
2342 field: "nonexistent".to_string(),
2343 operator: WhereOperator::In,
2344 value: Value::Array(vec![Value::I64(1)]),
2345 }),
2346 ..Default::default()
2347 };
2348
2349 let filter = DriveDocumentQueryFilter {
2350 contract: &contract,
2351 document_type_name: "niceDocument".to_string(),
2352 action_clauses: DocumentActionMatchClauses::Create {
2353 new_document_clauses: internal_clauses.clone(),
2354 },
2355 };
2356
2357 let id_value: Value = Identifier::from([1u8; 32]).into();
2358 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &BTreeMap::new()));
2359 }
2360
2361 #[test]
2364 fn matches_original_document_update_price_evaluates_original_clauses() {
2365 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2366 let contract = fixture.data_contract_owned();
2367
2368 let mut eq = BTreeMap::new();
2369 eq.insert(
2370 "kind".to_string(),
2371 WhereClause {
2372 field: "kind".to_string(),
2373 operator: WhereOperator::Equal,
2374 value: Value::Text("premium".to_string()),
2375 },
2376 );
2377
2378 let filter = DriveDocumentQueryFilter {
2379 contract: &contract,
2380 document_type_name: "niceDocument".to_string(),
2381 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2382 original_document_clauses: InternalClauses {
2383 equal_clauses: eq,
2384 ..Default::default()
2385 },
2386 price_clause: None,
2387 },
2388 };
2389
2390 let mut props = BTreeMap::new();
2392 props.insert("kind".to_string(), Value::Text("premium".to_string()));
2393 let doc = Document::V0(DocumentV0 {
2394 id: Identifier::from([15u8; 32]),
2395 owner_id: Identifier::from([0u8; 32]),
2396 properties: props,
2397 ..Default::default()
2398 });
2399 assert!(filter.matches_original_document(&doc));
2400
2401 let mut bad_props = BTreeMap::new();
2403 bad_props.insert("kind".to_string(), Value::Text("basic".to_string()));
2404 let bad_doc = Document::V0(DocumentV0 {
2405 id: Identifier::from([15u8; 32]),
2406 owner_id: Identifier::from([0u8; 32]),
2407 properties: bad_props,
2408 ..Default::default()
2409 });
2410 assert!(!filter.matches_original_document(&bad_doc));
2411 }
2412
2413 #[test]
2416 fn matches_original_document_purchase_evaluates_original_clauses() {
2417 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2418 let contract = fixture.data_contract_owned();
2419
2420 let mut eq = BTreeMap::new();
2421 eq.insert(
2422 "status".to_string(),
2423 WhereClause {
2424 field: "status".to_string(),
2425 operator: WhereOperator::Equal,
2426 value: Value::Text("for_sale".to_string()),
2427 },
2428 );
2429
2430 let filter = DriveDocumentQueryFilter {
2431 contract: &contract,
2432 document_type_name: "niceDocument".to_string(),
2433 action_clauses: DocumentActionMatchClauses::Purchase {
2434 original_document_clauses: InternalClauses {
2435 equal_clauses: eq,
2436 ..Default::default()
2437 },
2438 owner_clause: None,
2439 },
2440 };
2441
2442 let mut props = BTreeMap::new();
2443 props.insert("status".to_string(), Value::Text("for_sale".to_string()));
2444 let doc = Document::V0(DocumentV0 {
2445 id: Identifier::from([20u8; 32]),
2446 owner_id: Identifier::from([0u8; 32]),
2447 properties: props,
2448 ..Default::default()
2449 });
2450 assert!(filter.matches_original_document(&doc));
2451 }
2452
2453 #[test]
2456 fn validate_replace_with_invalid_original_clauses_fails() {
2457 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2458 let contract = fixture.data_contract_owned();
2459
2460 let mut eq = BTreeMap::new();
2462 eq.insert(
2463 "nonexistentField".to_string(),
2464 WhereClause {
2465 field: "nonexistentField".to_string(),
2466 operator: WhereOperator::Equal,
2467 value: Value::I64(1),
2468 },
2469 );
2470
2471 let filter = DriveDocumentQueryFilter {
2472 contract: &contract,
2473 document_type_name: "niceDocument".to_string(),
2474 action_clauses: DocumentActionMatchClauses::Replace {
2475 original_document_clauses: InternalClauses {
2476 equal_clauses: eq,
2477 ..Default::default()
2478 },
2479 new_document_clauses: InternalClauses::default(),
2480 },
2481 };
2482 assert!(filter.validate().is_err());
2483 }
2484
2485 #[test]
2488 fn validate_transfer_with_invalid_original_clauses_fails() {
2489 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2490 let contract = fixture.data_contract_owned();
2491
2492 let mut eq = BTreeMap::new();
2493 eq.insert(
2494 "badField".to_string(),
2495 WhereClause {
2496 field: "badField".to_string(),
2497 operator: WhereOperator::Equal,
2498 value: Value::I64(1),
2499 },
2500 );
2501
2502 let filter = DriveDocumentQueryFilter {
2503 contract: &contract,
2504 document_type_name: "niceDocument".to_string(),
2505 action_clauses: DocumentActionMatchClauses::Transfer {
2506 original_document_clauses: InternalClauses {
2507 equal_clauses: eq,
2508 ..Default::default()
2509 },
2510 owner_clause: None,
2511 },
2512 };
2513 assert!(filter.validate().is_err());
2514 }
2515
2516 #[test]
2519 fn validate_update_price_with_invalid_original_clauses_fails() {
2520 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2521 let contract = fixture.data_contract_owned();
2522
2523 let mut eq = BTreeMap::new();
2524 eq.insert(
2525 "badField".to_string(),
2526 WhereClause {
2527 field: "badField".to_string(),
2528 operator: WhereOperator::Equal,
2529 value: Value::I64(1),
2530 },
2531 );
2532
2533 let filter = DriveDocumentQueryFilter {
2534 contract: &contract,
2535 document_type_name: "niceDocument".to_string(),
2536 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2537 original_document_clauses: InternalClauses {
2538 equal_clauses: eq,
2539 ..Default::default()
2540 },
2541 price_clause: None,
2542 },
2543 };
2544 assert!(filter.validate().is_err());
2545 }
2546
2547 #[test]
2550 fn validate_purchase_with_invalid_original_clauses_fails() {
2551 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2552 let contract = fixture.data_contract_owned();
2553
2554 let mut eq = BTreeMap::new();
2555 eq.insert(
2556 "badField".to_string(),
2557 WhereClause {
2558 field: "badField".to_string(),
2559 operator: WhereOperator::Equal,
2560 value: Value::I64(1),
2561 },
2562 );
2563
2564 let filter = DriveDocumentQueryFilter {
2565 contract: &contract,
2566 document_type_name: "niceDocument".to_string(),
2567 action_clauses: DocumentActionMatchClauses::Purchase {
2568 original_document_clauses: InternalClauses {
2569 equal_clauses: eq,
2570 ..Default::default()
2571 },
2572 owner_clause: None,
2573 },
2574 };
2575 assert!(filter.validate().is_err());
2576 }
2577
2578 #[test]
2581 fn evaluate_clauses_starts_with_range_clause() {
2582 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2583 let contract = fixture.data_contract_owned();
2584
2585 let internal_clauses = InternalClauses {
2586 range_clause: Some(WhereClause {
2587 field: "name".to_string(),
2588 operator: WhereOperator::StartsWith,
2589 value: Value::Text("Ali".to_string()),
2590 }),
2591 ..Default::default()
2592 };
2593
2594 let filter = DriveDocumentQueryFilter {
2595 contract: &contract,
2596 document_type_name: "niceDocument".to_string(),
2597 action_clauses: DocumentActionMatchClauses::Create {
2598 new_document_clauses: internal_clauses.clone(),
2599 },
2600 };
2601
2602 let id_value: Value = Identifier::from([1u8; 32]).into();
2603
2604 let mut matching = BTreeMap::new();
2605 matching.insert("name".to_string(), Value::Text("Alice".to_string()));
2606 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &matching));
2607
2608 let mut non_matching = BTreeMap::new();
2609 non_matching.insert("name".to_string(), Value::Text("Bob".to_string()));
2610 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &non_matching));
2611 }
2612
2613 #[test]
2616 fn validate_price_clause_between_exclude_left_valid() {
2617 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2618 let contract = fixture.data_contract_owned();
2619
2620 let filter = DriveDocumentQueryFilter {
2621 contract: &contract,
2622 document_type_name: "niceDocument".to_string(),
2623 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2624 original_document_clauses: InternalClauses::default(),
2625 price_clause: Some(ValueClause {
2626 operator: WhereOperator::BetweenExcludeLeft,
2627 value: Value::Array(vec![Value::U64(5), Value::U64(50)]),
2628 }),
2629 },
2630 };
2631 assert!(filter.validate().is_valid());
2632 }
2633
2634 #[test]
2635 fn validate_price_clause_between_exclude_right_valid() {
2636 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2637 let contract = fixture.data_contract_owned();
2638
2639 let filter = DriveDocumentQueryFilter {
2640 contract: &contract,
2641 document_type_name: "niceDocument".to_string(),
2642 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2643 original_document_clauses: InternalClauses::default(),
2644 price_clause: Some(ValueClause {
2645 operator: WhereOperator::BetweenExcludeRight,
2646 value: Value::Array(vec![Value::U64(5), Value::U64(50)]),
2647 }),
2648 },
2649 };
2650 assert!(filter.validate().is_valid());
2651 }
2652
2653 #[test]
2654 fn validate_price_clause_between_exclude_bounds_valid() {
2655 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2656 let contract = fixture.data_contract_owned();
2657
2658 let filter = DriveDocumentQueryFilter {
2659 contract: &contract,
2660 document_type_name: "niceDocument".to_string(),
2661 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2662 original_document_clauses: InternalClauses::default(),
2663 price_clause: Some(ValueClause {
2664 operator: WhereOperator::BetweenExcludeBounds,
2665 value: Value::Array(vec![Value::U64(5), Value::U64(50)]),
2666 }),
2667 },
2668 };
2669 assert!(filter.validate().is_valid());
2670 }
2671
2672 #[test]
2675 fn validate_transfer_owner_clause_in_with_non_array_is_invalid() {
2676 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2677 let contract = fixture.data_contract_owned();
2678
2679 let filter = DriveDocumentQueryFilter {
2680 contract: &contract,
2681 document_type_name: "niceDocument".to_string(),
2682 action_clauses: DocumentActionMatchClauses::Transfer {
2683 original_document_clauses: InternalClauses::default(),
2684 owner_clause: Some(ValueClause {
2685 operator: WhereOperator::In,
2686 value: Value::Identifier([1u8; 32]),
2687 }),
2688 },
2689 };
2690 assert!(filter.validate().is_err());
2691 }
2692}