1use std::collections::BTreeMap;
34use dpp::data_contract::accessors::v0::DataContractV0Getters;
35use dpp::data_contract::DataContract;
36use dpp::platform_value::Value;
37use dpp::state_transition::batch_transition::batched_transition::document_transition::DocumentTransitionV0Methods;
38use dpp::document::{Document, DocumentV0Getters};
39use dpp::state_transition::batch_transition::batched_transition::document_transition::DocumentTransition;
40use dpp::state_transition::batch_transition::document_create_transition::v0::v0_methods::DocumentCreateTransitionV0Methods;
41use dpp::state_transition::batch_transition::document_base_transition::v0::v0_methods::DocumentBaseTransitionV0Methods;
42use dpp::state_transition::batch_transition::document_replace_transition::v0::v0_methods::DocumentReplaceTransitionV0Methods;
43use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::v0::v0_methods::DocumentTransferTransitionV0Methods;
44use dpp::state_transition::batch_transition::batched_transition::document_update_price_transition::v0::v0_methods::DocumentUpdatePriceTransitionV0Methods;
45use crate::query::{InternalClauses, QuerySyntaxSimpleValidationResult, ValueClause, WhereOperator};
46use crate::error::query::QuerySyntaxError;
47use dpp::platform_value::ValueMapHelper;
48
49#[cfg(any(feature = "server", feature = "verify"))]
56#[derive(Debug, PartialEq, Clone)]
57pub struct DriveDocumentQueryFilter<'a> {
58 pub contract: &'a DataContract,
60 pub document_type_name: String,
62 pub action_clauses: DocumentActionMatchClauses,
64}
65
66#[cfg(any(feature = "server", feature = "verify"))]
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum TransitionCheckResult {
70 Pass,
72 Fail,
74 NeedsOriginal,
76}
77
78#[allow(clippy::large_enum_variant)]
89#[derive(Debug, PartialEq, Clone)]
90pub enum DocumentActionMatchClauses {
91 Create {
93 new_document_clauses: InternalClauses,
95 },
96 Replace {
98 original_document_clauses: InternalClauses,
100 new_document_clauses: InternalClauses,
102 },
103 Delete {
105 original_document_clauses: InternalClauses,
107 },
108 Transfer {
110 original_document_clauses: InternalClauses,
112 owner_clause: Option<ValueClause>,
114 },
115 UpdatePrice {
117 original_document_clauses: InternalClauses,
119 price_clause: Option<ValueClause>,
121 },
122 Purchase {
124 original_document_clauses: InternalClauses,
126 owner_clause: Option<ValueClause>,
128 },
129}
130
131impl DriveDocumentQueryFilter<'_> {
132 #[cfg(any(feature = "server", feature = "verify"))]
145 pub fn matches_document_transition(
146 &self,
147 document_transition: &DocumentTransition,
148 batch_owner_value: Option<&Value>, ) -> TransitionCheckResult {
150 if document_transition.base().data_contract_id() != self.contract.id()
152 || document_transition.base().document_type_name() != &self.document_type_name
153 {
154 return TransitionCheckResult::Fail;
155 }
156
157 let id_value: Value = document_transition.base().id().into();
159
160 match document_transition {
161 DocumentTransition::Create(create) => {
162 if let DocumentActionMatchClauses::Create {
163 new_document_clauses,
164 } = &self.action_clauses
165 {
166 if self.evaluate_clauses(new_document_clauses, &id_value, create.data()) {
167 TransitionCheckResult::Pass
168 } else {
169 TransitionCheckResult::Fail
170 }
171 } else {
172 TransitionCheckResult::Fail
173 }
174 }
175 DocumentTransition::Replace(replace) => {
176 if let DocumentActionMatchClauses::Replace {
177 original_document_clauses,
178 new_document_clauses,
179 } = &self.action_clauses
180 {
181 let final_ok = if new_document_clauses.is_empty() {
182 true
183 } else {
184 self.evaluate_clauses(new_document_clauses, &id_value, replace.data())
185 };
186 if !final_ok {
187 return TransitionCheckResult::Fail;
188 }
189 if original_document_clauses.is_empty() {
190 return TransitionCheckResult::Pass;
191 }
192 if original_document_clauses.is_for_primary_key() {
193 if self.evaluate_clauses(
194 original_document_clauses,
195 &id_value,
196 &BTreeMap::new(),
197 ) {
198 return TransitionCheckResult::Pass;
199 }
200 return TransitionCheckResult::Fail;
201 }
202 TransitionCheckResult::NeedsOriginal
203 } else {
204 TransitionCheckResult::Fail
205 }
206 }
207 DocumentTransition::Delete(_) => {
208 if let DocumentActionMatchClauses::Delete {
209 original_document_clauses,
210 } = &self.action_clauses
211 {
212 if original_document_clauses.is_empty() {
213 return TransitionCheckResult::Pass;
214 }
215 if original_document_clauses.is_for_primary_key() {
216 if self.evaluate_clauses(
217 original_document_clauses,
218 &id_value,
219 &BTreeMap::new(),
220 ) {
221 return TransitionCheckResult::Pass;
222 }
223 return TransitionCheckResult::Fail;
224 }
225 TransitionCheckResult::NeedsOriginal
226 } else {
227 TransitionCheckResult::Fail
228 }
229 }
230 DocumentTransition::Transfer(transfer) => {
231 if let DocumentActionMatchClauses::Transfer {
232 original_document_clauses,
233 owner_clause,
234 } = &self.action_clauses
235 {
236 let new_owner_value: Value = transfer.recipient_owner_id().into();
237 let owner_ok = match owner_clause {
238 Some(clause) => clause.matches_value(&new_owner_value),
239 None => true,
240 };
241 if !owner_ok {
242 return TransitionCheckResult::Fail;
243 }
244 if original_document_clauses.is_empty() {
245 return TransitionCheckResult::Pass;
246 }
247 if original_document_clauses.is_for_primary_key() {
248 if self.evaluate_clauses(
249 original_document_clauses,
250 &id_value,
251 &BTreeMap::new(),
252 ) {
253 return TransitionCheckResult::Pass;
254 }
255 return TransitionCheckResult::Fail;
256 }
257 TransitionCheckResult::NeedsOriginal
258 } else {
259 TransitionCheckResult::Fail
260 }
261 }
262 DocumentTransition::UpdatePrice(update_price) => {
263 if let DocumentActionMatchClauses::UpdatePrice {
264 original_document_clauses,
265 price_clause,
266 } = &self.action_clauses
267 {
268 let price_value = Value::U64(update_price.price());
269 let price_ok = match price_clause {
270 Some(clause) => clause.matches_value(&price_value),
271 None => true,
272 };
273 if !price_ok {
274 return TransitionCheckResult::Fail;
275 }
276 if original_document_clauses.is_empty() {
277 return TransitionCheckResult::Pass;
278 }
279 if original_document_clauses.is_for_primary_key() {
280 if self.evaluate_clauses(
281 original_document_clauses,
282 &id_value,
283 &BTreeMap::new(),
284 ) {
285 return TransitionCheckResult::Pass;
286 }
287 return TransitionCheckResult::Fail;
288 }
289 TransitionCheckResult::NeedsOriginal
290 } else {
291 TransitionCheckResult::Fail
292 }
293 }
294 DocumentTransition::Purchase(_) => {
295 if let DocumentActionMatchClauses::Purchase {
296 original_document_clauses,
297 owner_clause,
298 } = &self.action_clauses
299 {
300 let owner_ok = match (owner_clause, batch_owner_value) {
301 (Some(clause), Some(val)) => clause.matches_value(val),
302 (Some(_), None) => return TransitionCheckResult::Fail,
303 (None, _) => true,
304 };
305 if !owner_ok {
306 return TransitionCheckResult::Fail;
307 }
308 if original_document_clauses.is_empty() {
309 return TransitionCheckResult::Pass;
310 }
311 if original_document_clauses.is_for_primary_key() {
312 if self.evaluate_clauses(
313 original_document_clauses,
314 &id_value,
315 &BTreeMap::new(),
316 ) {
317 return TransitionCheckResult::Pass;
318 }
319 return TransitionCheckResult::Fail;
320 }
321 TransitionCheckResult::NeedsOriginal
322 } else {
323 TransitionCheckResult::Fail
324 }
325 }
326 }
327 }
328
329 #[cfg(any(feature = "server", feature = "verify"))]
337 pub fn matches_original_document(&self, original_document: &Document) -> bool {
338 match &self.action_clauses {
340 DocumentActionMatchClauses::Replace {
341 original_document_clauses,
342 ..
343 }
344 | DocumentActionMatchClauses::Delete {
345 original_document_clauses,
346 }
347 | DocumentActionMatchClauses::Transfer {
348 original_document_clauses,
349 ..
350 }
351 | DocumentActionMatchClauses::UpdatePrice {
352 original_document_clauses,
353 ..
354 }
355 | DocumentActionMatchClauses::Purchase {
356 original_document_clauses,
357 ..
358 } => {
359 let id_value: Value = original_document.id().into();
360 self.evaluate_clauses(
361 original_document_clauses,
362 &id_value,
363 original_document.properties(),
364 )
365 }
366 _ => false,
367 }
368 }
369
370 #[cfg(any(feature = "server", feature = "verify"))]
372 fn evaluate_clauses(
373 &self,
374 clauses: &InternalClauses,
375 document_id_value: &Value,
376 document_data: &BTreeMap<String, Value>,
377 ) -> bool {
378 if let Some(primary_key_in_clause) = &clauses.primary_key_in_clause {
380 if !primary_key_in_clause.matches_value(document_id_value) {
381 return false;
382 }
383 }
384
385 if let Some(primary_key_equal_clause) = &clauses.primary_key_equal_clause {
387 if !primary_key_equal_clause.matches_value(document_id_value) {
388 return false;
389 }
390 }
391
392 if let Some(in_clause) = &clauses.in_clause {
394 let field_value = get_value_by_path(document_data, &in_clause.field);
395 if let Some(value) = field_value {
396 if !in_clause.matches_value(value) {
397 return false;
398 }
399 } else {
400 return false;
401 }
402 }
403
404 if let Some(range_clause) = &clauses.range_clause {
406 let field_value = get_value_by_path(document_data, &range_clause.field);
407 if let Some(value) = field_value {
408 if !range_clause.matches_value(value) {
409 return false;
410 }
411 } else {
412 return false;
413 }
414 }
415
416 for (field, equal_clause) in &clauses.equal_clauses {
418 let field_value = get_value_by_path(document_data, field);
419 if let Some(value) = field_value {
420 if !equal_clause.matches_value(value) {
421 return false;
422 }
423 } else {
424 return false;
425 }
426 }
427
428 true
429 }
430
431 #[cfg(any(feature = "server", feature = "verify"))]
435 pub fn validate(&self) -> QuerySyntaxSimpleValidationResult {
436 let Some(document_type) = self
438 .contract
439 .document_type_optional_for_name(&self.document_type_name)
440 else {
441 return QuerySyntaxSimpleValidationResult::new_with_error(
442 QuerySyntaxError::DocumentTypeNotFound("unknown document type"),
443 );
444 };
445
446 match &self.action_clauses {
447 DocumentActionMatchClauses::Create {
448 new_document_clauses,
449 } => new_document_clauses.validate_against_schema(document_type),
450 DocumentActionMatchClauses::Replace {
451 original_document_clauses,
452 new_document_clauses,
453 } => {
454 if !original_document_clauses.is_empty() {
455 let result = original_document_clauses.validate_against_schema(document_type);
456 if result.is_err() {
457 return result;
458 }
459 }
460 if !new_document_clauses.is_empty() {
461 new_document_clauses.validate_against_schema(document_type)
462 } else {
463 QuerySyntaxSimpleValidationResult::new()
464 }
465 }
466 DocumentActionMatchClauses::Delete {
467 original_document_clauses,
468 } => original_document_clauses.validate_against_schema(document_type),
469 DocumentActionMatchClauses::Transfer {
470 original_document_clauses,
471 owner_clause,
472 } => {
473 if !original_document_clauses.is_empty() {
474 let result = original_document_clauses.validate_against_schema(document_type);
475 if result.is_err() {
476 return result;
477 }
478 }
479 if let Some(owner) = owner_clause {
480 let ok = match owner.operator {
481 WhereOperator::Equal => matches!(owner.value, Value::Identifier(_)),
482 WhereOperator::In => match &owner.value {
483 Value::Array(arr) => {
484 arr.iter().all(|v| matches!(v, Value::Identifier(_)))
485 }
486 _ => false,
487 },
488 _ => false,
489 };
490 if ok {
491 QuerySyntaxSimpleValidationResult::new()
492 } else {
493 QuerySyntaxSimpleValidationResult::new_with_error(
494 QuerySyntaxError::InvalidWhereClauseComponents("invalid owner clause"),
495 )
496 }
497 } else {
498 QuerySyntaxSimpleValidationResult::new()
499 }
500 }
501 DocumentActionMatchClauses::UpdatePrice {
502 original_document_clauses,
503 price_clause,
504 } => {
505 if !original_document_clauses.is_empty() {
506 let result = original_document_clauses.validate_against_schema(document_type);
507 if result.is_err() {
508 return result;
509 }
510 }
511 if let Some(price) = price_clause {
512 let ok = match price.operator {
513 WhereOperator::Equal
514 | WhereOperator::GreaterThan
515 | WhereOperator::GreaterThanOrEquals
516 | WhereOperator::LessThan
517 | WhereOperator::LessThanOrEquals => {
518 price.value.is_integer_can_fit_in_64_bits()
519 }
520 WhereOperator::Between
521 | WhereOperator::BetweenExcludeBounds
522 | WhereOperator::BetweenExcludeLeft
523 | WhereOperator::BetweenExcludeRight => match &price.value {
524 Value::Array(arr) => {
525 arr.len() == 2
526 && arr.iter().all(|v| v.is_integer_can_fit_in_64_bits())
527 && arr[0] < arr[1]
528 }
529 _ => false,
530 },
531 WhereOperator::In => match &price.value {
532 Value::Array(arr) => {
533 arr.iter().all(|v| v.is_integer_can_fit_in_64_bits())
534 }
535 _ => false,
536 },
537 WhereOperator::StartsWith => false,
538 };
539 if ok {
540 QuerySyntaxSimpleValidationResult::new()
541 } else {
542 QuerySyntaxSimpleValidationResult::new_with_error(
543 QuerySyntaxError::InvalidWhereClauseComponents("invalid price clause"),
544 )
545 }
546 } else {
547 QuerySyntaxSimpleValidationResult::new()
548 }
549 }
550 DocumentActionMatchClauses::Purchase {
551 original_document_clauses,
552 owner_clause,
553 } => {
554 if !original_document_clauses.is_empty() {
555 let result = original_document_clauses.validate_against_schema(document_type);
556 if result.is_err() {
557 return result;
558 }
559 }
560 if let Some(owner) = owner_clause {
561 let ok = match owner.operator {
562 WhereOperator::Equal => matches!(owner.value, Value::Identifier(_)),
563 WhereOperator::In => match &owner.value {
564 Value::Array(arr) => {
565 arr.iter().all(|v| matches!(v, Value::Identifier(_)))
566 }
567 _ => false,
568 },
569 _ => false,
570 };
571 if ok {
572 QuerySyntaxSimpleValidationResult::new()
573 } else {
574 QuerySyntaxSimpleValidationResult::new_with_error(
575 QuerySyntaxError::InvalidWhereClauseComponents("invalid owner clause"),
576 )
577 }
578 } else {
579 QuerySyntaxSimpleValidationResult::new()
580 }
581 }
582 }
583 }
584}
585
586#[cfg(any(feature = "server", feature = "verify"))]
593fn get_value_by_path<'a>(root: &'a BTreeMap<String, Value>, path: &str) -> Option<&'a Value> {
594 if path.is_empty() {
595 return None;
596 }
597 let mut current: Option<&Value> = None;
598 let mut segments = path.split('.');
599 if let Some(first) = segments.next() {
600 current = root.get(first);
601 }
602 for seg in segments {
603 match current {
604 Some(Value::Map(ref vm)) => {
605 current = vm.get_optional_key(seg);
606 }
607 _ => return None,
608 }
609 }
610 current
611}
612
613#[cfg(test)]
614mod tests {
615 use super::*;
616 use crate::query::{ValueClause, WhereClause, WhereOperator};
617 use dpp::document::{Document, DocumentV0};
618 use dpp::prelude::Identifier;
619 use dpp::state_transition::batch_transition::document_base_transition::v1::DocumentBaseTransitionV1;
620 use dpp::state_transition::batch_transition::document_base_transition::DocumentBaseTransition;
621 use dpp::tests::fixtures::get_data_contract_fixture;
622 use dpp::version::LATEST_PLATFORM_VERSION;
623
624 #[test]
625 fn test_matches_document_basic() {
626 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
628 let contract = fixture.data_contract_owned();
629
630 let internal_clauses = InternalClauses::default();
632 let filter = DriveDocumentQueryFilter {
633 contract: &contract,
634 document_type_name: "niceDocument".to_string(),
635 action_clauses: DocumentActionMatchClauses::Create {
636 new_document_clauses: internal_clauses.clone(),
637 },
638 };
639
640 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
642 id: Identifier::from([3u8; 32]),
643 document_type_name: "niceDocument".to_string(),
644 data_contract_id: contract.id(),
645 identity_contract_nonce: 0,
646 token_payment_info: None,
647 });
648
649 let document_data = BTreeMap::new();
650
651 let id_value: Value = document_base.id().into();
653 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &document_data));
654 }
655
656 #[test]
657 fn test_matches_document_with_primary_key_equal() {
658 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
659 let contract = fixture.data_contract_owned();
660
661 let target_id = Identifier::from([42u8; 32]);
662
663 let internal_clauses = InternalClauses {
664 primary_key_equal_clause: Some(WhereClause {
665 field: "$id".to_string(),
666 operator: WhereOperator::Equal,
667 value: target_id.into(),
668 }),
669 ..Default::default()
670 };
671
672 let filter = DriveDocumentQueryFilter {
673 contract: &contract,
674 document_type_name: "niceDocument".to_string(),
675 action_clauses: DocumentActionMatchClauses::Create {
676 new_document_clauses: internal_clauses.clone(),
677 },
678 };
679
680 let matching_doc = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
682 id: target_id,
683 document_type_name: "niceDocument".to_string(),
684 data_contract_id: contract.id(),
685 identity_contract_nonce: 0,
686 token_payment_info: None,
687 });
688
689 let document_data = BTreeMap::new();
690
691 let id_value: Value = matching_doc.id().into();
692 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &document_data));
693
694 let non_matching_doc = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
696 id: Identifier::from([99u8; 32]),
697 document_type_name: "niceDocument".to_string(),
698 data_contract_id: contract.id(),
699 identity_contract_nonce: 0,
700 token_payment_info: None,
701 });
702
703 let non_id_value: Value = non_matching_doc.id().into();
704 assert!(!filter.evaluate_clauses(&internal_clauses, &non_id_value, &document_data));
705 }
706
707 #[test]
708 fn test_matches_document_with_field_filters() {
709 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
710 let contract = fixture.data_contract_owned();
711
712 let mut equal_clauses = BTreeMap::new();
714 equal_clauses.insert(
715 "name".to_string(),
716 WhereClause {
717 field: "name".to_string(),
718 operator: WhereOperator::Equal,
719 value: Value::Text("example".to_string()),
720 },
721 );
722
723 let internal_clauses = InternalClauses {
724 equal_clauses,
725 ..Default::default()
726 };
727
728 let filter = DriveDocumentQueryFilter {
729 contract: &contract,
730 document_type_name: "niceDocument".to_string(),
731 action_clauses: DocumentActionMatchClauses::Create {
732 new_document_clauses: internal_clauses.clone(),
733 },
734 };
735
736 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
737 id: Identifier::from([3u8; 32]),
738 document_type_name: "niceDocument".to_string(),
739 data_contract_id: contract.id(),
740 identity_contract_nonce: 0,
741 token_payment_info: None,
742 });
743
744 let mut matching_data = BTreeMap::new();
746 matching_data.insert("name".to_string(), Value::Text("example".to_string()));
747
748 let id_value: Value = document_base.id().into();
749 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &matching_data));
750
751 let mut non_matching_data = BTreeMap::new();
753 non_matching_data.insert("name".to_string(), Value::Text("different".to_string()));
754
755 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &non_matching_data));
756
757 let empty_data = BTreeMap::new();
759 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &empty_data));
760 }
761
762 #[test]
763 fn test_matches_document_with_in_operator() {
764 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
765 let contract = fixture.data_contract_owned();
766
767 let allowed_values = vec![
768 Value::Text("active".to_string()),
769 Value::Text("pending".to_string()),
770 ];
771
772 let internal_clauses = InternalClauses {
773 in_clause: Some(WhereClause {
774 field: "status".to_string(),
775 operator: WhereOperator::In,
776 value: Value::Array(allowed_values),
777 }),
778 ..Default::default()
779 };
780
781 let filter = DriveDocumentQueryFilter {
782 contract: &contract,
783 document_type_name: "niceDocument".to_string(),
784 action_clauses: DocumentActionMatchClauses::Create {
785 new_document_clauses: internal_clauses.clone(),
786 },
787 };
788
789 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
790 id: Identifier::from([3u8; 32]),
791 document_type_name: "niceDocument".to_string(),
792 data_contract_id: contract.id(),
793 identity_contract_nonce: 0,
794 token_payment_info: None,
795 });
796
797 let mut matching_data = BTreeMap::new();
799 matching_data.insert("status".to_string(), Value::Text("active".to_string()));
800 let id_value: Value = document_base.id().into();
801 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &matching_data));
802
803 let mut non_matching_data = BTreeMap::new();
805 non_matching_data.insert("status".to_string(), Value::Text("completed".to_string()));
806 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &non_matching_data));
807 }
808
809 #[test]
810 fn test_matches_document_with_range_operators() {
811 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
812 let contract = fixture.data_contract_owned();
813
814 let internal_clauses = InternalClauses {
816 range_clause: Some(WhereClause {
817 field: "score".to_string(),
818 operator: WhereOperator::GreaterThan,
819 value: Value::U64(50),
820 }),
821 ..Default::default()
822 };
823
824 let filter = DriveDocumentQueryFilter {
825 contract: &contract,
826 document_type_name: "niceDocument".to_string(),
827 action_clauses: DocumentActionMatchClauses::Create {
828 new_document_clauses: internal_clauses.clone(),
829 },
830 };
831
832 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
833 id: Identifier::from([3u8; 32]),
834 document_type_name: "niceDocument".to_string(),
835 data_contract_id: contract.id(),
836 identity_contract_nonce: 0,
837 token_payment_info: None,
838 });
839
840 let mut greater_data = BTreeMap::new();
842 greater_data.insert("score".to_string(), Value::U64(75));
843 let id_value: Value = document_base.id().into();
844 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &greater_data));
845
846 let mut equal_data = BTreeMap::new();
848 equal_data.insert("score".to_string(), Value::U64(50));
849 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &equal_data));
850
851 let mut less_data = BTreeMap::new();
853 less_data.insert("score".to_string(), Value::U64(25));
854 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &less_data));
855 }
856
857 #[test]
858 fn test_matches_document_with_nested_field() {
859 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
860 let contract = fixture.data_contract_owned();
861
862 let mut equal_clauses = BTreeMap::new();
864 equal_clauses.insert(
865 "meta.status".to_string(),
866 WhereClause {
867 field: "meta.status".to_string(),
868 operator: WhereOperator::Equal,
869 value: Value::Text("active".to_string()),
870 },
871 );
872
873 let internal_clauses = InternalClauses {
874 equal_clauses,
875 ..Default::default()
876 };
877
878 let filter = DriveDocumentQueryFilter {
879 contract: &contract,
880 document_type_name: "niceDocument".to_string(),
881 action_clauses: DocumentActionMatchClauses::Create {
882 new_document_clauses: internal_clauses.clone(),
883 },
884 };
885
886 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
887 id: Identifier::from([3u8; 32]),
888 document_type_name: "niceDocument".to_string(),
889 data_contract_id: contract.id(),
890 identity_contract_nonce: 0,
891 token_payment_info: None,
892 });
893
894 let nested = vec![(
896 Value::Text("status".to_string()),
897 Value::Text("active".to_string()),
898 )];
899 let mut data = BTreeMap::new();
900 data.insert("meta".to_string(), Value::Map(nested));
901
902 let id_value: Value = document_base.id().into();
903 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &data));
904 }
905
906 #[test]
907 fn test_validate_optional_actions_allow_empty_clauses() {
908 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
909 let contract = fixture.data_contract_owned();
910
911 let filter = DriveDocumentQueryFilter {
913 contract: &contract,
914 document_type_name: "niceDocument".to_string(),
915 action_clauses: DocumentActionMatchClauses::Replace {
916 original_document_clauses: InternalClauses::default(),
917 new_document_clauses: InternalClauses::default(),
918 },
919 };
920 assert!(filter.validate().is_valid());
921
922 let filter = DriveDocumentQueryFilter {
924 contract: &contract,
925 document_type_name: "niceDocument".to_string(),
926 action_clauses: DocumentActionMatchClauses::Replace {
927 original_document_clauses: InternalClauses::default(),
928 new_document_clauses: InternalClauses {
929 primary_key_equal_clause: Some(WhereClause {
930 field: "$id".to_string(),
931 operator: WhereOperator::Equal,
932 value: Value::Identifier([3u8; 32]),
933 }),
934 ..Default::default()
935 },
936 },
937 };
938 assert!(filter.validate().is_valid());
939
940 let filter = DriveDocumentQueryFilter {
942 contract: &contract,
943 document_type_name: "niceDocument".to_string(),
944 action_clauses: DocumentActionMatchClauses::Transfer {
945 original_document_clauses: InternalClauses::default(),
946 owner_clause: None,
947 },
948 };
949 assert!(filter.validate().is_valid());
950
951 let filter = DriveDocumentQueryFilter {
953 contract: &contract,
954 document_type_name: "niceDocument".to_string(),
955 action_clauses: DocumentActionMatchClauses::Transfer {
956 original_document_clauses: InternalClauses::default(),
957 owner_clause: Some(ValueClause {
958 operator: WhereOperator::Equal,
959 value: Value::Identifier([1u8; 32]),
960 }),
961 },
962 };
963 assert!(filter.validate().is_valid());
964
965 let filter = DriveDocumentQueryFilter {
967 contract: &contract,
968 document_type_name: "niceDocument".to_string(),
969 action_clauses: DocumentActionMatchClauses::UpdatePrice {
970 original_document_clauses: InternalClauses::default(),
971 price_clause: None,
972 },
973 };
974 assert!(filter.validate().is_valid());
975
976 let filter = DriveDocumentQueryFilter {
978 contract: &contract,
979 document_type_name: "niceDocument".to_string(),
980 action_clauses: DocumentActionMatchClauses::UpdatePrice {
981 original_document_clauses: InternalClauses::default(),
982 price_clause: Some(ValueClause {
983 operator: WhereOperator::GreaterThan,
984 value: Value::U64(0),
985 }),
986 },
987 };
988 assert!(filter.validate().is_valid());
989
990 let filter = DriveDocumentQueryFilter {
992 contract: &contract,
993 document_type_name: "niceDocument".to_string(),
994 action_clauses: DocumentActionMatchClauses::Purchase {
995 original_document_clauses: InternalClauses::default(),
996 owner_clause: None,
997 },
998 };
999 assert!(filter.validate().is_valid());
1000
1001 let filter = DriveDocumentQueryFilter {
1003 contract: &contract,
1004 document_type_name: "niceDocument".to_string(),
1005 action_clauses: DocumentActionMatchClauses::Purchase {
1006 original_document_clauses: InternalClauses::default(),
1007 owner_clause: Some(ValueClause {
1008 operator: WhereOperator::Equal,
1009 value: Value::Identifier([2u8; 32]),
1010 }),
1011 },
1012 };
1013 assert!(filter.validate().is_valid());
1014 }
1015
1016 #[test]
1017 fn test_transfer_owner_clause_only_matches() {
1018 use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::v0::DocumentTransferTransitionV0;
1019 use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::DocumentTransferTransition;
1020
1021 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1022 let contract = fixture.data_contract_owned();
1023
1024 let new_owner = Identifier::from([5u8; 32]);
1025
1026 let filter = DriveDocumentQueryFilter {
1028 contract: &contract,
1029 document_type_name: "niceDocument".to_string(),
1030 action_clauses: DocumentActionMatchClauses::Transfer {
1031 original_document_clauses: InternalClauses::default(),
1032 owner_clause: Some(ValueClause {
1033 operator: WhereOperator::Equal,
1034 value: new_owner.into(),
1035 }),
1036 },
1037 };
1038
1039 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1041 id: Identifier::from([3u8; 32]),
1042 document_type_name: "niceDocument".to_string(),
1043 data_contract_id: contract.id(),
1044 identity_contract_nonce: 0,
1045 token_payment_info: None,
1046 });
1047
1048 let transfer_v0 = DocumentTransferTransitionV0 {
1049 base: document_base.clone(),
1050 revision: 1_u64,
1051 recipient_owner_id: new_owner,
1052 };
1053 let transfer = DocumentTransition::Transfer(DocumentTransferTransition::V0(transfer_v0));
1054
1055 assert_eq!(
1057 filter.matches_document_transition(&transfer, None),
1058 TransitionCheckResult::Pass
1059 );
1060
1061 let other_owner = Identifier::from([6u8; 32]);
1063 let transfer_v0_mismatch = DocumentTransferTransitionV0 {
1064 base: document_base,
1065 revision: 1_u64,
1066 recipient_owner_id: other_owner,
1067 };
1068 let transfer_mismatch =
1069 DocumentTransition::Transfer(DocumentTransferTransition::V0(transfer_v0_mismatch));
1070 assert_eq!(
1071 filter.matches_document_transition(&transfer_mismatch, None),
1072 TransitionCheckResult::Fail
1073 );
1074 }
1075
1076 #[test]
1077 fn test_purchase_owner_clause_only_matches_and_requires_owner_context() {
1078 use dpp::fee::Credits;
1079 use dpp::state_transition::batch_transition::batched_transition::document_purchase_transition::v0::DocumentPurchaseTransitionV0;
1080 use dpp::state_transition::batch_transition::batched_transition::document_purchase_transition::DocumentPurchaseTransition;
1081
1082 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1083 let contract = fixture.data_contract_owned();
1084
1085 let purchaser = Identifier::from([7u8; 32]);
1086
1087 let filter = DriveDocumentQueryFilter {
1089 contract: &contract,
1090 document_type_name: "niceDocument".to_string(),
1091 action_clauses: DocumentActionMatchClauses::Purchase {
1092 original_document_clauses: InternalClauses::default(),
1093 owner_clause: Some(ValueClause {
1094 operator: WhereOperator::Equal,
1095 value: purchaser.into(),
1096 }),
1097 },
1098 };
1099
1100 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1102 id: Identifier::from([4u8; 32]),
1103 document_type_name: "niceDocument".to_string(),
1104 data_contract_id: contract.id(),
1105 identity_contract_nonce: 0,
1106 token_payment_info: None,
1107 });
1108
1109 let purchase_v0 = DocumentPurchaseTransitionV0 {
1110 base: document_base,
1111 revision: 1_u64,
1112 price: 10 as Credits,
1113 };
1114 let purchase = DocumentTransition::Purchase(DocumentPurchaseTransition::V0(purchase_v0));
1115
1116 assert_eq!(
1118 filter.matches_document_transition(&purchase, None),
1119 TransitionCheckResult::Fail
1120 );
1121 let owner_value = Value::Identifier(purchaser.to_buffer());
1123 assert_eq!(
1124 filter.matches_document_transition(&purchase, Some(&owner_value)),
1125 TransitionCheckResult::Pass
1126 );
1127 }
1128
1129 #[test]
1130 fn test_transfer_original_clause_only_matches_with_original_document() {
1131 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1132 let contract = fixture.data_contract_owned();
1133
1134 let mut eq = BTreeMap::new();
1136 eq.insert(
1137 "status".to_string(),
1138 WhereClause {
1139 field: "status".to_string(),
1140 operator: WhereOperator::Equal,
1141 value: Value::Text("active".to_string()),
1142 },
1143 );
1144 let filter = DriveDocumentQueryFilter {
1145 contract: &contract,
1146 document_type_name: "niceDocument".to_string(),
1147 action_clauses: DocumentActionMatchClauses::Transfer {
1148 original_document_clauses: InternalClauses {
1149 equal_clauses: eq,
1150 ..Default::default()
1151 },
1152 owner_clause: None,
1153 },
1154 };
1155
1156 let mut original = BTreeMap::new();
1158 original.insert("status".to_string(), Value::Text("active".to_string()));
1159 let original_doc = Document::V0(DocumentV0 {
1160 id: Identifier::from([9u8; 32]),
1161 owner_id: Identifier::from([0u8; 32]),
1162 properties: original,
1163 ..Default::default()
1164 });
1165 assert!(filter.matches_original_document(&original_doc));
1166
1167 }
1170
1171 #[test]
1172 fn test_delete_original_clause_only_matches_with_original_document() {
1173 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1174 let contract = fixture.data_contract_owned();
1175
1176 let mut eq = BTreeMap::new();
1178 eq.insert(
1179 "status".to_string(),
1180 WhereClause {
1181 field: "status".to_string(),
1182 operator: WhereOperator::Equal,
1183 value: Value::Text("active".to_string()),
1184 },
1185 );
1186 let filter = DriveDocumentQueryFilter {
1187 contract: &contract,
1188 document_type_name: "niceDocument".to_string(),
1189 action_clauses: DocumentActionMatchClauses::Delete {
1190 original_document_clauses: InternalClauses {
1191 equal_clauses: eq,
1192 ..Default::default()
1193 },
1194 },
1195 };
1196
1197 let mut original = BTreeMap::new();
1199 original.insert("status".to_string(), Value::Text("active".to_string()));
1200 let original_doc = Document::V0(DocumentV0 {
1201 id: Identifier::from([12u8; 32]),
1202 owner_id: Identifier::from([0u8; 32]),
1203 properties: original,
1204 ..Default::default()
1205 });
1206 assert!(filter.matches_original_document(&original_doc));
1207
1208 let mut original_bad = BTreeMap::new();
1213 original_bad.insert("status".to_string(), Value::Text("inactive".to_string()));
1214 let original_doc_bad = Document::V0(DocumentV0 {
1215 id: Identifier::from([12u8; 32]),
1216 owner_id: Identifier::from([0u8; 32]),
1217 properties: original_bad,
1218 ..Default::default()
1219 });
1220 assert!(!filter.matches_original_document(&original_doc_bad));
1221 }
1222
1223 #[test]
1224 fn test_update_price_price_clause_only_matches_and_with_original_clause() {
1225 use dpp::fee::Credits;
1226 use dpp::state_transition::batch_transition::batched_transition::document_update_price_transition::v0::DocumentUpdatePriceTransitionV0;
1227 use dpp::state_transition::batch_transition::batched_transition::document_update_price_transition::DocumentUpdatePriceTransition;
1228
1229 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1230 let contract = fixture.data_contract_owned();
1231
1232 let base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1233 id: Identifier::from([10u8; 32]),
1234 document_type_name: "niceDocument".to_string(),
1235 data_contract_id: contract.id(),
1236 identity_contract_nonce: 0,
1237 token_payment_info: None,
1238 });
1239
1240 let update_v0 = DocumentUpdatePriceTransitionV0 {
1242 base: base.clone(),
1243 revision: 1,
1244 price: 10 as Credits,
1245 };
1246 let update = DocumentTransition::UpdatePrice(DocumentUpdatePriceTransition::V0(update_v0));
1247
1248 let filter_price_only = DriveDocumentQueryFilter {
1249 contract: &contract,
1250 document_type_name: "niceDocument".to_string(),
1251 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1252 original_document_clauses: InternalClauses::default(),
1253 price_clause: Some(ValueClause {
1254 operator: WhereOperator::GreaterThan,
1255 value: Value::U64(5),
1256 }),
1257 },
1258 };
1259 assert_eq!(
1261 filter_price_only.matches_document_transition(&update, None),
1262 TransitionCheckResult::Pass
1263 );
1264
1265 let filter_price_only_fail = DriveDocumentQueryFilter {
1266 contract: &contract,
1267 document_type_name: "niceDocument".to_string(),
1268 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1269 original_document_clauses: InternalClauses::default(),
1270 price_clause: Some(ValueClause {
1271 operator: WhereOperator::GreaterThan,
1272 value: Value::U64(15),
1273 }),
1274 },
1275 };
1276 assert_eq!(
1277 filter_price_only_fail.matches_document_transition(&update, None),
1278 TransitionCheckResult::Fail
1279 );
1280
1281 let mut eq = BTreeMap::new();
1283 eq.insert(
1284 "kind".to_string(),
1285 WhereClause {
1286 field: "kind".to_string(),
1287 operator: WhereOperator::Equal,
1288 value: Value::Text("sale".to_string()),
1289 },
1290 );
1291 let filter_with_orig = DriveDocumentQueryFilter {
1292 contract: &contract,
1293 document_type_name: "niceDocument".to_string(),
1294 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1295 original_document_clauses: InternalClauses {
1296 equal_clauses: eq,
1297 ..Default::default()
1298 },
1299 price_clause: Some(ValueClause {
1300 operator: WhereOperator::GreaterThanOrEquals,
1301 value: Value::U64(10),
1302 }),
1303 },
1304 };
1305 let mut original_doc = BTreeMap::new();
1306 original_doc.insert("kind".to_string(), Value::Text("sale".to_string()));
1307 assert_eq!(
1308 filter_with_orig.matches_document_transition(&update, None),
1309 TransitionCheckResult::NeedsOriginal
1310 );
1311 let original_document = Document::V0(DocumentV0 {
1312 id: Identifier::from([10u8; 32]),
1313 owner_id: Identifier::from([0u8; 32]),
1314 properties: original_doc,
1315 ..Default::default()
1316 });
1317 assert!(filter_with_orig.matches_original_document(&original_document));
1318
1319 }
1322
1323 #[test]
1324 fn test_replace_with_both_original_and_new_document_clauses() {
1325 use dpp::state_transition::batch_transition::batched_transition::document_replace_transition::v0::DocumentReplaceTransitionV0;
1326 use dpp::state_transition::batch_transition::batched_transition::document_replace_transition::DocumentReplaceTransition;
1327
1328 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1329 let contract = fixture.data_contract_owned();
1330
1331 let base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1332 id: Identifier::from([11u8; 32]),
1333 document_type_name: "niceDocument".to_string(),
1334 data_contract_id: contract.id(),
1335 identity_contract_nonce: 0,
1336 token_payment_info: None,
1337 });
1338
1339 let mut orig_eq = BTreeMap::new();
1341 orig_eq.insert(
1342 "status".to_string(),
1343 WhereClause {
1344 field: "status".to_string(),
1345 operator: WhereOperator::Equal,
1346 value: Value::Text("active".to_string()),
1347 },
1348 );
1349 let original_clauses = InternalClauses {
1350 equal_clauses: orig_eq,
1351 ..Default::default()
1352 };
1353
1354 let mut final_eq = BTreeMap::new();
1355 final_eq.insert(
1356 "score".to_string(),
1357 WhereClause {
1358 field: "score".to_string(),
1359 operator: WhereOperator::Equal,
1360 value: Value::U64(10),
1361 },
1362 );
1363 let new_document_clauses = InternalClauses {
1364 equal_clauses: final_eq,
1365 ..Default::default()
1366 };
1367
1368 let filter = DriveDocumentQueryFilter {
1369 contract: &contract,
1370 document_type_name: "niceDocument".to_string(),
1371 action_clauses: DocumentActionMatchClauses::Replace {
1372 original_document_clauses: original_clauses,
1373 new_document_clauses,
1374 },
1375 };
1376
1377 let mut data = BTreeMap::new();
1379 data.insert("score".to_string(), Value::U64(10));
1380 let replace_v0 = DocumentReplaceTransitionV0 {
1381 base,
1382 revision: 1,
1383 data,
1384 };
1385 let replace = DocumentTransition::Replace(DocumentReplaceTransition::V0(replace_v0));
1386
1387 let mut original_doc = BTreeMap::new();
1389 original_doc.insert("status".to_string(), Value::Text("active".to_string()));
1390 assert_eq!(
1391 filter.matches_document_transition(&replace, None),
1392 TransitionCheckResult::NeedsOriginal
1393 );
1394 let original_document = Document::V0(DocumentV0 {
1395 id: Identifier::from([11u8; 32]),
1396 owner_id: Identifier::from([0u8; 32]),
1397 properties: original_doc,
1398 ..Default::default()
1399 });
1400 assert!(filter.matches_original_document(&original_document));
1401
1402 let mut original_doc_bad = BTreeMap::new();
1407 original_doc_bad.insert("status".to_string(), Value::Text("inactive".to_string()));
1408 let original_document_bad = Document::V0(DocumentV0 {
1409 id: Identifier::from([11u8; 32]),
1410 owner_id: Identifier::from([0u8; 32]),
1411 properties: original_doc_bad,
1412 ..Default::default()
1413 });
1414 assert!(!filter.matches_original_document(&original_document_bad));
1415
1416 if let DocumentTransition::Replace(mut rep) = replace.clone() {
1418 let DocumentReplaceTransition::V0(ref mut v0) = rep;
1419 v0.data.insert("score".to_string(), Value::U64(9));
1420 let bad_final = DocumentTransition::Replace(rep);
1421 assert_eq!(
1422 filter.matches_document_transition(&bad_final, None),
1423 TransitionCheckResult::Fail
1424 );
1425 }
1426 }
1427
1428 #[test]
1429 fn test_matches_document_with_between_operator() {
1430 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1431 let contract = fixture.data_contract_owned();
1432
1433 let internal_clauses = InternalClauses {
1434 range_clause: Some(WhereClause {
1435 field: "value".to_string(),
1436 operator: WhereOperator::Between,
1437 value: Value::Array(vec![Value::U64(10), Value::U64(20)]),
1438 }),
1439 ..Default::default()
1440 };
1441
1442 let filter = DriveDocumentQueryFilter {
1443 contract: &contract,
1444 document_type_name: "niceDocument".to_string(),
1445 action_clauses: DocumentActionMatchClauses::Create {
1446 new_document_clauses: internal_clauses.clone(),
1447 },
1448 };
1449
1450 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1451 id: Identifier::from([3u8; 32]),
1452 document_type_name: "niceDocument".to_string(),
1453 data_contract_id: contract.id(),
1454 identity_contract_nonce: 0,
1455 token_payment_info: None,
1456 });
1457
1458 let mut in_range = BTreeMap::new();
1460 in_range.insert("value".to_string(), Value::U64(15));
1461 let id_value: Value = document_base.id().into();
1462 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &in_range));
1463
1464 let mut lower_bound = BTreeMap::new();
1466 lower_bound.insert("value".to_string(), Value::U64(10));
1467 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &lower_bound));
1468
1469 let mut upper_bound = BTreeMap::new();
1471 upper_bound.insert("value".to_string(), Value::U64(20));
1472 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &upper_bound));
1473
1474 let mut below = BTreeMap::new();
1476 below.insert("value".to_string(), Value::U64(5));
1477 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &below));
1478
1479 let mut above = BTreeMap::new();
1481 above.insert("value".to_string(), Value::U64(25));
1482 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &above));
1483 }
1484
1485 #[test]
1486 fn test_validate_filter() {
1487 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1488 let contract = fixture.data_contract_owned();
1489
1490 let mut equal_clauses = BTreeMap::new();
1492 equal_clauses.insert(
1493 "firstName".to_string(),
1494 WhereClause {
1495 field: "firstName".to_string(),
1496 operator: WhereOperator::Equal,
1497 value: Value::Text("Alice".to_string()),
1498 },
1499 );
1500 let internal_clauses = InternalClauses {
1501 equal_clauses,
1502 ..Default::default()
1503 };
1504
1505 let valid_filter = DriveDocumentQueryFilter {
1506 contract: &contract,
1507 document_type_name: "indexedDocument".to_string(),
1508 action_clauses: DocumentActionMatchClauses::Create {
1509 new_document_clauses: internal_clauses,
1510 },
1511 };
1512
1513 assert!(
1514 valid_filter.validate().is_valid(),
1515 "Filter with indexed field should be valid"
1516 );
1517
1518 let mut equal_clauses = BTreeMap::new();
1521 equal_clauses.insert(
1522 "name".to_string(),
1523 WhereClause {
1524 field: "name".to_string(),
1525 operator: WhereOperator::Equal,
1526 value: Value::Text("value".to_string()),
1527 },
1528 );
1529 let internal_clauses = InternalClauses {
1530 equal_clauses,
1531 ..Default::default()
1532 };
1533
1534 let invalid_filter = DriveDocumentQueryFilter {
1535 contract: &contract,
1536 document_type_name: "niceDocument".to_string(),
1537 action_clauses: DocumentActionMatchClauses::Create {
1538 new_document_clauses: internal_clauses,
1539 },
1540 };
1541
1542 assert!(
1543 invalid_filter.validate().is_valid(),
1544 "Structural validate should ignore indexes"
1545 );
1546 let internal_clauses = InternalClauses {
1550 primary_key_equal_clause: Some(WhereClause {
1551 field: "$id".to_string(),
1552 operator: WhereOperator::Equal,
1553 value: Value::Identifier([42u8; 32]),
1554 }),
1555 ..Default::default()
1556 };
1557
1558 let primary_key_filter = DriveDocumentQueryFilter {
1559 contract: &contract,
1560 document_type_name: "indexedDocument".to_string(),
1561 action_clauses: DocumentActionMatchClauses::Create {
1562 new_document_clauses: internal_clauses,
1563 },
1564 };
1565
1566 assert!(
1567 primary_key_filter.validate().is_valid(),
1568 "Filter with only primary key should be valid"
1569 );
1570 }
1571
1572 #[test]
1573 fn test_validate_rejects_id_in_generic_clauses() {
1574 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1575 let contract = fixture.data_contract_owned();
1576
1577 let mut eq = BTreeMap::new();
1579 eq.insert(
1580 "$id".to_string(),
1581 WhereClause {
1582 field: "$id".to_string(),
1583 operator: WhereOperator::Equal,
1584 value: Value::Identifier([1u8; 32]),
1585 },
1586 );
1587 let filter = DriveDocumentQueryFilter {
1588 contract: &contract,
1589 document_type_name: "niceDocument".to_string(),
1590 action_clauses: DocumentActionMatchClauses::Create {
1591 new_document_clauses: InternalClauses {
1592 equal_clauses: eq,
1593 ..Default::default()
1594 },
1595 },
1596 };
1597 assert!(filter.validate().is_err());
1598
1599 let filter = DriveDocumentQueryFilter {
1601 contract: &contract,
1602 document_type_name: "niceDocument".to_string(),
1603 action_clauses: DocumentActionMatchClauses::Create {
1604 new_document_clauses: InternalClauses {
1605 range_clause: Some(WhereClause {
1606 field: "$id".to_string(),
1607 operator: WhereOperator::GreaterThan,
1608 value: Value::U64(0),
1609 }),
1610 ..Default::default()
1611 },
1612 },
1613 };
1614 assert!(filter.validate().is_err());
1615 }
1616
1617 #[test]
1618 fn test_validate_owner_and_price_clause_types() {
1619 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1620 let contract = fixture.data_contract_owned();
1621
1622 let filter = DriveDocumentQueryFilter {
1624 contract: &contract,
1625 document_type_name: "niceDocument".to_string(),
1626 action_clauses: DocumentActionMatchClauses::Transfer {
1627 original_document_clauses: InternalClauses::default(),
1628 owner_clause: Some(ValueClause {
1629 operator: WhereOperator::Equal,
1630 value: Value::Text("not-id".to_string()),
1631 }),
1632 },
1633 };
1634 assert!(filter.validate().is_err());
1635
1636 let filter = DriveDocumentQueryFilter {
1638 contract: &contract,
1639 document_type_name: "niceDocument".to_string(),
1640 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1641 original_document_clauses: InternalClauses::default(),
1642 price_clause: Some(ValueClause {
1643 operator: WhereOperator::Equal,
1644 value: Value::Float(1.23),
1645 }),
1646 },
1647 };
1648 assert!(filter.validate().is_err());
1649
1650 let filter = DriveDocumentQueryFilter {
1652 contract: &contract,
1653 document_type_name: "niceDocument".to_string(),
1654 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1655 original_document_clauses: InternalClauses::default(),
1656 price_clause: Some(ValueClause {
1657 operator: WhereOperator::Between,
1658 value: Value::Array(vec![Value::U64(1), Value::Float(2.0)]),
1659 }),
1660 },
1661 };
1662 assert!(filter.validate().is_err());
1663
1664 for operator in [
1666 WhereOperator::Between,
1667 WhereOperator::BetweenExcludeBounds,
1668 WhereOperator::BetweenExcludeLeft,
1669 WhereOperator::BetweenExcludeRight,
1670 ] {
1671 let filter = DriveDocumentQueryFilter {
1672 contract: &contract,
1673 document_type_name: "niceDocument".to_string(),
1674 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1675 original_document_clauses: InternalClauses::default(),
1676 price_clause: Some(ValueClause {
1677 operator,
1678 value: Value::Array(vec![Value::U64(10), Value::U64(10)]),
1679 }),
1680 },
1681 };
1682 assert!(
1683 filter.validate().is_err(),
1684 "{operator:?} should reject equal price bounds"
1685 );
1686 }
1687 }
1688
1689 #[test]
1690 fn test_validate_startswith_on_numeric_field_rejected() {
1691 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1692 let contract = fixture.data_contract_owned();
1693
1694 let filter = DriveDocumentQueryFilter {
1696 contract: &contract,
1697 document_type_name: "niceDocument".to_string(),
1698 action_clauses: DocumentActionMatchClauses::Create {
1699 new_document_clauses: InternalClauses {
1700 range_clause: Some(WhereClause {
1701 field: "score".to_string(),
1702 operator: WhereOperator::StartsWith,
1703 value: Value::Text("1".to_string()),
1704 }),
1705 ..Default::default()
1706 },
1707 },
1708 };
1709 assert!(filter.validate().is_err());
1710 }
1711
1712 #[test]
1713 fn test_conversion_between_filter_and_query() {
1714 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1715 let contract = fixture.data_contract_owned();
1716
1717 let internal_clauses = InternalClauses {
1718 primary_key_equal_clause: Some(WhereClause {
1719 field: "$id".to_string(),
1720 operator: WhereOperator::Equal,
1721 value: Value::Identifier([42u8; 32]),
1722 }),
1723 ..Default::default()
1724 };
1725
1726 let original_filter = DriveDocumentQueryFilter {
1727 contract: &contract,
1728 document_type_name: "niceDocument".to_string(),
1729 action_clauses: DocumentActionMatchClauses::Create {
1730 new_document_clauses: internal_clauses.clone(),
1731 },
1732 };
1733
1734 if let DocumentActionMatchClauses::Create {
1736 new_document_clauses,
1737 } = original_filter.action_clauses
1738 {
1739 assert_eq!(new_document_clauses, internal_clauses);
1740 } else {
1741 panic!("expected Create action clauses");
1742 }
1743 }
1744}