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