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