1use std::borrow::Cow;
38use std::collections::BTreeMap;
39use dpp::data_contract::accessors::v0::DataContractV0Getters;
40use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
41use dpp::data_contract::document_type::methods::DocumentTypeBasicMethods;
42use dpp::data_contract::document_type::typed_array_property;
43use dpp::data_contract::DataContract;
44use dpp::platform_value::Value;
45use dpp::state_transition::batch_transition::batched_transition::document_transition::DocumentTransitionV0Methods;
46use dpp::document::{Document, DocumentV0Getters};
47use dpp::state_transition::batch_transition::batched_transition::document_transition::DocumentTransition;
48use dpp::state_transition::batch_transition::document_create_transition::v0::v0_methods::DocumentCreateTransitionV0Methods;
49use dpp::state_transition::batch_transition::document_base_transition::v0::v0_methods::DocumentBaseTransitionV0Methods;
50use dpp::state_transition::batch_transition::document_replace_transition::v0::v0_methods::DocumentReplaceTransitionV0Methods;
51use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::v0::v0_methods::DocumentTransferTransitionV0Methods;
52use dpp::state_transition::batch_transition::batched_transition::document_index_only_delete_transition::v0::v0_methods::DocumentIndexOnlyDeleteTransitionV0Methods;
53use dpp::state_transition::batch_transition::batched_transition::document_update_price_transition::v0::v0_methods::DocumentUpdatePriceTransitionV0Methods;
54use crate::query::{
55 InternalClauses, QuerySyntaxSimpleValidationResult, ValueClause, WhereClause, WhereOperator,
56};
57use crate::error::query::QuerySyntaxError;
58use dpp::platform_value::ValueMapHelper;
59use dpp::version::PlatformVersion;
60
61#[cfg(any(feature = "server", feature = "verify"))]
68#[derive(Debug, PartialEq, Clone)]
69pub struct DriveDocumentQueryFilter<'a> {
70 pub contract: &'a DataContract,
72 pub document_type_name: String,
74 pub action_clauses: DocumentActionMatchClauses,
76}
77
78#[cfg(any(feature = "server", feature = "verify"))]
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum TransitionCheckResult {
82 Pass,
84 Fail,
86 NeedsOriginal,
88}
89
90#[allow(clippy::large_enum_variant)]
101#[derive(Debug, PartialEq, Clone)]
102pub enum DocumentActionMatchClauses {
103 Create {
105 new_document_clauses: InternalClauses,
107 },
108 Replace {
110 original_document_clauses: InternalClauses,
112 new_document_clauses: InternalClauses,
114 },
115 Delete {
117 original_document_clauses: InternalClauses,
119 },
120 Transfer {
122 original_document_clauses: InternalClauses,
124 owner_clause: Option<ValueClause>,
126 },
127 UpdatePrice {
129 original_document_clauses: InternalClauses,
131 price_clause: Option<ValueClause>,
133 },
134 Purchase {
136 original_document_clauses: InternalClauses,
138 owner_clause: Option<ValueClause>,
140 },
141}
142
143impl DriveDocumentQueryFilter<'_> {
144 #[cfg(any(feature = "server", feature = "verify"))]
161 pub fn matches_document_transition(
162 &self,
163 document_transition: &DocumentTransition,
164 batch_owner_value: Option<&Value>, platform_version: &PlatformVersion,
166 ) -> TransitionCheckResult {
167 if document_transition.base().data_contract_id() != self.contract.id()
169 || document_transition.base().document_type_name() != &self.document_type_name
170 {
171 return TransitionCheckResult::Fail;
172 }
173
174 let id_value: Value = document_transition.base().id().into();
176
177 match document_transition {
178 DocumentTransition::Create(create) => {
179 if let DocumentActionMatchClauses::Create {
180 new_document_clauses,
181 } = &self.action_clauses
182 {
183 let Some(data) = self.data_as_stored(create.data(), platform_version) else {
184 return TransitionCheckResult::Fail;
185 };
186 if self.evaluate_clauses(new_document_clauses, &id_value, &data) {
187 TransitionCheckResult::Pass
188 } else {
189 TransitionCheckResult::Fail
190 }
191 } else {
192 TransitionCheckResult::Fail
193 }
194 }
195 DocumentTransition::Replace(replace) => {
196 if let DocumentActionMatchClauses::Replace {
197 original_document_clauses,
198 new_document_clauses,
199 } = &self.action_clauses
200 {
201 let final_ok = if new_document_clauses.is_empty() {
202 true
203 } else {
204 let Some(data) = self.data_as_stored(replace.data(), platform_version)
205 else {
206 return TransitionCheckResult::Fail;
207 };
208 self.evaluate_clauses(new_document_clauses, &id_value, &data)
209 };
210 if !final_ok {
211 return TransitionCheckResult::Fail;
212 }
213 if original_document_clauses.is_empty() {
214 return TransitionCheckResult::Pass;
215 }
216 if original_document_clauses.is_for_primary_key() {
217 if self.evaluate_clauses(
218 original_document_clauses,
219 &id_value,
220 &BTreeMap::new(),
221 ) {
222 return TransitionCheckResult::Pass;
223 }
224 return TransitionCheckResult::Fail;
225 }
226 TransitionCheckResult::NeedsOriginal
227 } else {
228 TransitionCheckResult::Fail
229 }
230 }
231 DocumentTransition::Delete(_) => {
232 if let DocumentActionMatchClauses::Delete {
233 original_document_clauses,
234 } = &self.action_clauses
235 {
236 if original_document_clauses.is_empty() {
237 return TransitionCheckResult::Pass;
238 }
239 if original_document_clauses.is_for_primary_key() {
240 if self.evaluate_clauses(
241 original_document_clauses,
242 &id_value,
243 &BTreeMap::new(),
244 ) {
245 return TransitionCheckResult::Pass;
246 }
247 return TransitionCheckResult::Fail;
248 }
249 TransitionCheckResult::NeedsOriginal
250 } else {
251 TransitionCheckResult::Fail
252 }
253 }
254 DocumentTransition::Transfer(transfer) => {
255 if let DocumentActionMatchClauses::Transfer {
256 original_document_clauses,
257 owner_clause,
258 } = &self.action_clauses
259 {
260 let new_owner_value: Value = transfer.recipient_owner_id().into();
261 let owner_ok = match owner_clause {
262 Some(clause) => clause.matches_value(&new_owner_value),
263 None => true,
264 };
265 if !owner_ok {
266 return TransitionCheckResult::Fail;
267 }
268 if original_document_clauses.is_empty() {
269 return TransitionCheckResult::Pass;
270 }
271 if original_document_clauses.is_for_primary_key() {
272 if self.evaluate_clauses(
273 original_document_clauses,
274 &id_value,
275 &BTreeMap::new(),
276 ) {
277 return TransitionCheckResult::Pass;
278 }
279 return TransitionCheckResult::Fail;
280 }
281 TransitionCheckResult::NeedsOriginal
282 } else {
283 TransitionCheckResult::Fail
284 }
285 }
286 DocumentTransition::UpdatePrice(update_price) => {
287 if let DocumentActionMatchClauses::UpdatePrice {
288 original_document_clauses,
289 price_clause,
290 } = &self.action_clauses
291 {
292 let price_value = Value::U64(update_price.price());
293 let price_ok = match price_clause {
294 Some(clause) => clause.matches_value(&price_value),
295 None => true,
296 };
297 if !price_ok {
298 return TransitionCheckResult::Fail;
299 }
300 if original_document_clauses.is_empty() {
301 return TransitionCheckResult::Pass;
302 }
303 if original_document_clauses.is_for_primary_key() {
304 if self.evaluate_clauses(
305 original_document_clauses,
306 &id_value,
307 &BTreeMap::new(),
308 ) {
309 return TransitionCheckResult::Pass;
310 }
311 return TransitionCheckResult::Fail;
312 }
313 TransitionCheckResult::NeedsOriginal
314 } else {
315 TransitionCheckResult::Fail
316 }
317 }
318 DocumentTransition::Purchase(_) => {
319 if let DocumentActionMatchClauses::Purchase {
320 original_document_clauses,
321 owner_clause,
322 } = &self.action_clauses
323 {
324 let owner_ok = match (owner_clause, batch_owner_value) {
325 (Some(clause), Some(val)) => clause.matches_value(val),
326 (Some(_), None) => return TransitionCheckResult::Fail,
327 (None, _) => true,
328 };
329 if !owner_ok {
330 return TransitionCheckResult::Fail;
331 }
332 if original_document_clauses.is_empty() {
333 return TransitionCheckResult::Pass;
334 }
335 if original_document_clauses.is_for_primary_key() {
336 if self.evaluate_clauses(
337 original_document_clauses,
338 &id_value,
339 &BTreeMap::new(),
340 ) {
341 return TransitionCheckResult::Pass;
342 }
343 return TransitionCheckResult::Fail;
344 }
345 TransitionCheckResult::NeedsOriginal
346 } else {
347 TransitionCheckResult::Fail
348 }
349 }
350 DocumentTransition::IndexOnlyDelete(index_only_delete) => {
351 if let DocumentActionMatchClauses::Delete {
352 original_document_clauses,
353 } = &self.action_clauses
354 {
355 let Some(values) =
361 self.data_as_stored(index_only_delete.data(), platform_version)
362 else {
363 return TransitionCheckResult::Fail;
364 };
365 if self.evaluate_clauses(original_document_clauses, &id_value, &values) {
366 TransitionCheckResult::Pass
367 } else {
368 TransitionCheckResult::Fail
369 }
370 } else {
371 TransitionCheckResult::Fail
372 }
373 }
374 }
375 }
376
377 #[cfg(any(feature = "server", feature = "verify"))]
385 pub fn matches_original_document(&self, original_document: &Document) -> bool {
386 match &self.action_clauses {
388 DocumentActionMatchClauses::Replace {
389 original_document_clauses,
390 ..
391 }
392 | DocumentActionMatchClauses::Delete {
393 original_document_clauses,
394 }
395 | DocumentActionMatchClauses::Transfer {
396 original_document_clauses,
397 ..
398 }
399 | DocumentActionMatchClauses::UpdatePrice {
400 original_document_clauses,
401 ..
402 }
403 | DocumentActionMatchClauses::Purchase {
404 original_document_clauses,
405 ..
406 } => {
407 let id_value: Value = original_document.id().into();
408 self.evaluate_clauses(
409 original_document_clauses,
410 &id_value,
411 original_document.properties(),
412 )
413 }
414 _ => false,
415 }
416 }
417
418 #[cfg(any(feature = "server", feature = "verify"))]
423 fn data_as_stored<'d>(
424 &self,
425 data: &'d BTreeMap<String, Value>,
426 platform_version: &PlatformVersion,
427 ) -> Option<Cow<'d, BTreeMap<String, Value>>> {
428 let document_type = self
429 .contract
430 .document_type_for_name(&self.document_type_name)
431 .ok()?;
432 document_type.data_as_stored(data, platform_version).ok()
433 }
434
435 #[cfg(any(feature = "server", feature = "verify"))]
442 fn clause_matches(&self, clause: &WhereClause, value: &Value) -> bool {
443 if !clause.operator.reads_array_elements() {
444 return clause.matches_value(value);
445 }
446 let Some(document_type) = self
447 .contract
448 .document_type_optional_for_name(&self.document_type_name)
449 else {
450 return false;
451 };
452 let Some(typed_array) =
453 typed_array_property(document_type.flattened_properties(), &clause.field)
454 else {
455 return false;
456 };
457 let key = |value: &Value| typed_array.item_type.encode_value_for_tree_keys(value).ok();
458 let wanted: Vec<Vec<u8>> = if clause.operator == WhereOperator::ContainsAny {
459 match clause.in_values().data {
460 Some(values) => values.iter().filter_map(key).collect(),
461 None => return false,
462 }
463 } else {
464 key(&clause.value).into_iter().collect()
465 };
466 let Value::Array(elements) = value else {
467 return false;
468 };
469 elements
470 .iter()
471 .filter_map(key)
472 .any(|element| wanted.contains(&element))
473 }
474
475 #[cfg(any(feature = "server", feature = "verify"))]
477 fn evaluate_clauses(
478 &self,
479 clauses: &InternalClauses,
480 document_id_value: &Value,
481 document_data: &BTreeMap<String, Value>,
482 ) -> bool {
483 if let Some(primary_key_in_clause) = &clauses.primary_key_in_clause {
485 if !primary_key_in_clause.matches_value(document_id_value) {
486 return false;
487 }
488 }
489
490 if let Some(primary_key_equal_clause) = &clauses.primary_key_equal_clause {
492 if !primary_key_equal_clause.matches_value(document_id_value) {
493 return false;
494 }
495 }
496
497 for in_clause in &clauses.in_clauses {
499 let field_value = get_value_by_path(document_data, &in_clause.field);
500 if let Some(value) = field_value {
501 if !self.clause_matches(in_clause, value) {
502 return false;
503 }
504 } else {
505 return false;
506 }
507 }
508
509 if let Some(range_clause) = &clauses.range_clause {
511 let field_value = get_value_by_path(document_data, &range_clause.field);
512 if let Some(value) = field_value {
513 if !range_clause.matches_value(value) {
514 return false;
515 }
516 } else {
517 return false;
518 }
519 }
520
521 for (field, equal_clause) in &clauses.equal_clauses {
523 let field_value = get_value_by_path(document_data, field);
524 if let Some(value) = field_value {
525 if !self.clause_matches(equal_clause, value) {
526 return false;
527 }
528 } else {
529 return false;
530 }
531 }
532
533 true
534 }
535
536 #[cfg(any(feature = "server", feature = "verify"))]
540 pub fn validate(&self) -> QuerySyntaxSimpleValidationResult {
541 let Some(document_type) = self
543 .contract
544 .document_type_optional_for_name(&self.document_type_name)
545 else {
546 return QuerySyntaxSimpleValidationResult::new_with_error(
547 QuerySyntaxError::DocumentTypeNotFound("unknown document type"),
548 );
549 };
550
551 match &self.action_clauses {
552 DocumentActionMatchClauses::Create {
553 new_document_clauses,
554 } => new_document_clauses.validate_against_schema(document_type),
555 DocumentActionMatchClauses::Replace {
556 original_document_clauses,
557 new_document_clauses,
558 } => {
559 if !original_document_clauses.is_empty() {
560 let result = original_document_clauses.validate_against_schema(document_type);
561 if result.is_err() {
562 return result;
563 }
564 }
565 if !new_document_clauses.is_empty() {
566 new_document_clauses.validate_against_schema(document_type)
567 } else {
568 QuerySyntaxSimpleValidationResult::new()
569 }
570 }
571 DocumentActionMatchClauses::Delete {
572 original_document_clauses,
573 } => original_document_clauses.validate_against_schema(document_type),
574 DocumentActionMatchClauses::Transfer {
575 original_document_clauses,
576 owner_clause,
577 } => {
578 if !original_document_clauses.is_empty() {
579 let result = original_document_clauses.validate_against_schema(document_type);
580 if result.is_err() {
581 return result;
582 }
583 }
584 if let Some(owner) = owner_clause {
585 let ok = match owner.operator {
586 WhereOperator::Equal => matches!(owner.value, Value::Identifier(_)),
587 WhereOperator::In => match &owner.value {
588 Value::Array(arr) => {
589 arr.iter().all(|v| matches!(v, Value::Identifier(_)))
590 }
591 _ => false,
592 },
593 _ => false,
594 };
595 if ok {
596 QuerySyntaxSimpleValidationResult::new()
597 } else {
598 QuerySyntaxSimpleValidationResult::new_with_error(
599 QuerySyntaxError::InvalidWhereClauseComponents("invalid owner clause"),
600 )
601 }
602 } else {
603 QuerySyntaxSimpleValidationResult::new()
604 }
605 }
606 DocumentActionMatchClauses::UpdatePrice {
607 original_document_clauses,
608 price_clause,
609 } => {
610 if !original_document_clauses.is_empty() {
611 let result = original_document_clauses.validate_against_schema(document_type);
612 if result.is_err() {
613 return result;
614 }
615 }
616 if let Some(price) = price_clause {
617 let ok = match price.operator {
618 WhereOperator::Equal
619 | WhereOperator::GreaterThan
620 | WhereOperator::GreaterThanOrEquals
621 | WhereOperator::LessThan
622 | WhereOperator::LessThanOrEquals => {
623 price.value.is_integer_can_fit_in_64_bits()
624 }
625 WhereOperator::Between
626 | WhereOperator::BetweenExcludeBounds
627 | WhereOperator::BetweenExcludeLeft
628 | WhereOperator::BetweenExcludeRight => match &price.value {
629 Value::Array(arr) => {
630 arr.len() == 2
631 && arr.iter().all(|v| v.is_integer_can_fit_in_64_bits())
632 && arr[0] < arr[1]
633 }
634 _ => false,
635 },
636 WhereOperator::In => match &price.value {
637 Value::Array(arr) => {
638 arr.iter().all(|v| v.is_integer_can_fit_in_64_bits())
639 }
640 _ => false,
641 },
642 WhereOperator::StartsWith
643 | WhereOperator::Contains
644 | WhereOperator::ContainsAny => false,
645 };
646 if ok {
647 QuerySyntaxSimpleValidationResult::new()
648 } else {
649 QuerySyntaxSimpleValidationResult::new_with_error(
650 QuerySyntaxError::InvalidWhereClauseComponents("invalid price clause"),
651 )
652 }
653 } else {
654 QuerySyntaxSimpleValidationResult::new()
655 }
656 }
657 DocumentActionMatchClauses::Purchase {
658 original_document_clauses,
659 owner_clause,
660 } => {
661 if !original_document_clauses.is_empty() {
662 let result = original_document_clauses.validate_against_schema(document_type);
663 if result.is_err() {
664 return result;
665 }
666 }
667 if let Some(owner) = owner_clause {
668 let ok = match owner.operator {
669 WhereOperator::Equal => matches!(owner.value, Value::Identifier(_)),
670 WhereOperator::In => match &owner.value {
671 Value::Array(arr) => {
672 arr.iter().all(|v| matches!(v, Value::Identifier(_)))
673 }
674 _ => false,
675 },
676 _ => false,
677 };
678 if ok {
679 QuerySyntaxSimpleValidationResult::new()
680 } else {
681 QuerySyntaxSimpleValidationResult::new_with_error(
682 QuerySyntaxError::InvalidWhereClauseComponents("invalid owner clause"),
683 )
684 }
685 } else {
686 QuerySyntaxSimpleValidationResult::new()
687 }
688 }
689 }
690 }
691}
692
693#[cfg(any(feature = "server", feature = "verify"))]
700fn get_value_by_path<'a>(root: &'a BTreeMap<String, Value>, path: &str) -> Option<&'a Value> {
701 if path.is_empty() {
702 return None;
703 }
704 let mut current: Option<&Value> = None;
705 let mut segments = path.split('.');
706 if let Some(first) = segments.next() {
707 current = root.get(first);
708 }
709 for seg in segments {
710 match current {
711 Some(Value::Map(ref vm)) => {
712 current = vm.get_optional_key(seg);
713 }
714 _ => return None,
715 }
716 }
717 current
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723 use crate::query::{ValueClause, WhereClause, WhereOperator};
724 use dpp::document::{Document, DocumentV0};
725 use dpp::prelude::Identifier;
726 use dpp::state_transition::batch_transition::document_base_transition::v1::DocumentBaseTransitionV1;
727 use dpp::state_transition::batch_transition::document_base_transition::DocumentBaseTransition;
728 use dpp::tests::fixtures::get_data_contract_fixture;
729 use dpp::version::LATEST_PLATFORM_VERSION;
730
731 #[test]
732 fn test_matches_document_basic() {
733 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
735 let contract = fixture.data_contract_owned();
736
737 let internal_clauses = InternalClauses::default();
739 let filter = DriveDocumentQueryFilter {
740 contract: &contract,
741 document_type_name: "niceDocument".to_string(),
742 action_clauses: DocumentActionMatchClauses::Create {
743 new_document_clauses: internal_clauses.clone(),
744 },
745 };
746
747 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
749 id: Identifier::from([3u8; 32]),
750 document_type_name: "niceDocument".to_string(),
751 data_contract_id: contract.id(),
752 identity_contract_nonce: 0,
753 token_payment_info: None,
754 });
755
756 let document_data = BTreeMap::new();
757
758 let id_value: Value = document_base.id().into();
760 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &document_data));
761 }
762
763 #[test]
764 fn test_matches_document_with_primary_key_equal() {
765 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
766 let contract = fixture.data_contract_owned();
767
768 let target_id = Identifier::from([42u8; 32]);
769
770 let internal_clauses = InternalClauses {
771 primary_key_equal_clause: Some(WhereClause {
772 field: "$id".to_string(),
773 operator: WhereOperator::Equal,
774 value: target_id.into(),
775 }),
776 ..Default::default()
777 };
778
779 let filter = DriveDocumentQueryFilter {
780 contract: &contract,
781 document_type_name: "niceDocument".to_string(),
782 action_clauses: DocumentActionMatchClauses::Create {
783 new_document_clauses: internal_clauses.clone(),
784 },
785 };
786
787 let matching_doc = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
789 id: target_id,
790 document_type_name: "niceDocument".to_string(),
791 data_contract_id: contract.id(),
792 identity_contract_nonce: 0,
793 token_payment_info: None,
794 });
795
796 let document_data = BTreeMap::new();
797
798 let id_value: Value = matching_doc.id().into();
799 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &document_data));
800
801 let non_matching_doc = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
803 id: Identifier::from([99u8; 32]),
804 document_type_name: "niceDocument".to_string(),
805 data_contract_id: contract.id(),
806 identity_contract_nonce: 0,
807 token_payment_info: None,
808 });
809
810 let non_id_value: Value = non_matching_doc.id().into();
811 assert!(!filter.evaluate_clauses(&internal_clauses, &non_id_value, &document_data));
812 }
813
814 #[test]
815 fn test_matches_document_with_field_filters() {
816 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
817 let contract = fixture.data_contract_owned();
818
819 let mut equal_clauses = BTreeMap::new();
821 equal_clauses.insert(
822 "name".to_string(),
823 WhereClause {
824 field: "name".to_string(),
825 operator: WhereOperator::Equal,
826 value: Value::Text("example".to_string()),
827 },
828 );
829
830 let internal_clauses = InternalClauses {
831 equal_clauses,
832 ..Default::default()
833 };
834
835 let filter = DriveDocumentQueryFilter {
836 contract: &contract,
837 document_type_name: "niceDocument".to_string(),
838 action_clauses: DocumentActionMatchClauses::Create {
839 new_document_clauses: internal_clauses.clone(),
840 },
841 };
842
843 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
844 id: Identifier::from([3u8; 32]),
845 document_type_name: "niceDocument".to_string(),
846 data_contract_id: contract.id(),
847 identity_contract_nonce: 0,
848 token_payment_info: None,
849 });
850
851 let mut matching_data = BTreeMap::new();
853 matching_data.insert("name".to_string(), Value::Text("example".to_string()));
854
855 let id_value: Value = document_base.id().into();
856 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &matching_data));
857
858 let mut non_matching_data = BTreeMap::new();
860 non_matching_data.insert("name".to_string(), Value::Text("different".to_string()));
861
862 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &non_matching_data));
863
864 let empty_data = BTreeMap::new();
866 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &empty_data));
867 }
868
869 #[test]
870 fn test_matches_document_with_in_operator() {
871 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
872 let contract = fixture.data_contract_owned();
873
874 let allowed_values = vec![
875 Value::Text("active".to_string()),
876 Value::Text("pending".to_string()),
877 ];
878
879 let internal_clauses = InternalClauses {
880 in_clauses: vec![WhereClause {
881 field: "status".to_string(),
882 operator: WhereOperator::In,
883 value: Value::Array(allowed_values),
884 }],
885 ..Default::default()
886 };
887
888 let filter = DriveDocumentQueryFilter {
889 contract: &contract,
890 document_type_name: "niceDocument".to_string(),
891 action_clauses: DocumentActionMatchClauses::Create {
892 new_document_clauses: internal_clauses.clone(),
893 },
894 };
895
896 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
897 id: Identifier::from([3u8; 32]),
898 document_type_name: "niceDocument".to_string(),
899 data_contract_id: contract.id(),
900 identity_contract_nonce: 0,
901 token_payment_info: None,
902 });
903
904 let mut matching_data = BTreeMap::new();
906 matching_data.insert("status".to_string(), Value::Text("active".to_string()));
907 let id_value: Value = document_base.id().into();
908 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &matching_data));
909
910 let mut non_matching_data = BTreeMap::new();
912 non_matching_data.insert("status".to_string(), Value::Text("completed".to_string()));
913 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &non_matching_data));
914 }
915
916 #[test]
917 fn test_matches_document_with_range_operators() {
918 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
919 let contract = fixture.data_contract_owned();
920
921 let internal_clauses = InternalClauses {
923 range_clause: Some(WhereClause {
924 field: "score".to_string(),
925 operator: WhereOperator::GreaterThan,
926 value: Value::U64(50),
927 }),
928 ..Default::default()
929 };
930
931 let filter = DriveDocumentQueryFilter {
932 contract: &contract,
933 document_type_name: "niceDocument".to_string(),
934 action_clauses: DocumentActionMatchClauses::Create {
935 new_document_clauses: internal_clauses.clone(),
936 },
937 };
938
939 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
940 id: Identifier::from([3u8; 32]),
941 document_type_name: "niceDocument".to_string(),
942 data_contract_id: contract.id(),
943 identity_contract_nonce: 0,
944 token_payment_info: None,
945 });
946
947 let mut greater_data = BTreeMap::new();
949 greater_data.insert("score".to_string(), Value::U64(75));
950 let id_value: Value = document_base.id().into();
951 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &greater_data));
952
953 let mut equal_data = BTreeMap::new();
955 equal_data.insert("score".to_string(), Value::U64(50));
956 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &equal_data));
957
958 let mut less_data = BTreeMap::new();
960 less_data.insert("score".to_string(), Value::U64(25));
961 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &less_data));
962 }
963
964 #[test]
965 fn test_matches_document_with_nested_field() {
966 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
967 let contract = fixture.data_contract_owned();
968
969 let mut equal_clauses = BTreeMap::new();
971 equal_clauses.insert(
972 "meta.status".to_string(),
973 WhereClause {
974 field: "meta.status".to_string(),
975 operator: WhereOperator::Equal,
976 value: Value::Text("active".to_string()),
977 },
978 );
979
980 let internal_clauses = InternalClauses {
981 equal_clauses,
982 ..Default::default()
983 };
984
985 let filter = DriveDocumentQueryFilter {
986 contract: &contract,
987 document_type_name: "niceDocument".to_string(),
988 action_clauses: DocumentActionMatchClauses::Create {
989 new_document_clauses: internal_clauses.clone(),
990 },
991 };
992
993 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
994 id: Identifier::from([3u8; 32]),
995 document_type_name: "niceDocument".to_string(),
996 data_contract_id: contract.id(),
997 identity_contract_nonce: 0,
998 token_payment_info: None,
999 });
1000
1001 let nested = vec![(
1003 Value::Text("status".to_string()),
1004 Value::Text("active".to_string()),
1005 )];
1006 let mut data = BTreeMap::new();
1007 data.insert("meta".to_string(), Value::Map(nested));
1008
1009 let id_value: Value = document_base.id().into();
1010 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &data));
1011 }
1012
1013 #[test]
1014 fn test_validate_optional_actions_allow_empty_clauses() {
1015 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1016 let contract = fixture.data_contract_owned();
1017
1018 let filter = DriveDocumentQueryFilter {
1020 contract: &contract,
1021 document_type_name: "niceDocument".to_string(),
1022 action_clauses: DocumentActionMatchClauses::Replace {
1023 original_document_clauses: InternalClauses::default(),
1024 new_document_clauses: InternalClauses::default(),
1025 },
1026 };
1027 assert!(filter.validate().is_valid());
1028
1029 let filter = DriveDocumentQueryFilter {
1031 contract: &contract,
1032 document_type_name: "niceDocument".to_string(),
1033 action_clauses: DocumentActionMatchClauses::Replace {
1034 original_document_clauses: InternalClauses::default(),
1035 new_document_clauses: InternalClauses {
1036 primary_key_equal_clause: Some(WhereClause {
1037 field: "$id".to_string(),
1038 operator: WhereOperator::Equal,
1039 value: Value::Identifier([3u8; 32]),
1040 }),
1041 ..Default::default()
1042 },
1043 },
1044 };
1045 assert!(filter.validate().is_valid());
1046
1047 let filter = DriveDocumentQueryFilter {
1049 contract: &contract,
1050 document_type_name: "niceDocument".to_string(),
1051 action_clauses: DocumentActionMatchClauses::Transfer {
1052 original_document_clauses: InternalClauses::default(),
1053 owner_clause: None,
1054 },
1055 };
1056 assert!(filter.validate().is_valid());
1057
1058 let filter = DriveDocumentQueryFilter {
1060 contract: &contract,
1061 document_type_name: "niceDocument".to_string(),
1062 action_clauses: DocumentActionMatchClauses::Transfer {
1063 original_document_clauses: InternalClauses::default(),
1064 owner_clause: Some(ValueClause {
1065 operator: WhereOperator::Equal,
1066 value: Value::Identifier([1u8; 32]),
1067 }),
1068 },
1069 };
1070 assert!(filter.validate().is_valid());
1071
1072 let filter = DriveDocumentQueryFilter {
1074 contract: &contract,
1075 document_type_name: "niceDocument".to_string(),
1076 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1077 original_document_clauses: InternalClauses::default(),
1078 price_clause: None,
1079 },
1080 };
1081 assert!(filter.validate().is_valid());
1082
1083 let filter = DriveDocumentQueryFilter {
1085 contract: &contract,
1086 document_type_name: "niceDocument".to_string(),
1087 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1088 original_document_clauses: InternalClauses::default(),
1089 price_clause: Some(ValueClause {
1090 operator: WhereOperator::GreaterThan,
1091 value: Value::U64(0),
1092 }),
1093 },
1094 };
1095 assert!(filter.validate().is_valid());
1096
1097 let filter = DriveDocumentQueryFilter {
1099 contract: &contract,
1100 document_type_name: "niceDocument".to_string(),
1101 action_clauses: DocumentActionMatchClauses::Purchase {
1102 original_document_clauses: InternalClauses::default(),
1103 owner_clause: None,
1104 },
1105 };
1106 assert!(filter.validate().is_valid());
1107
1108 let filter = DriveDocumentQueryFilter {
1110 contract: &contract,
1111 document_type_name: "niceDocument".to_string(),
1112 action_clauses: DocumentActionMatchClauses::Purchase {
1113 original_document_clauses: InternalClauses::default(),
1114 owner_clause: Some(ValueClause {
1115 operator: WhereOperator::Equal,
1116 value: Value::Identifier([2u8; 32]),
1117 }),
1118 },
1119 };
1120 assert!(filter.validate().is_valid());
1121 }
1122
1123 #[test]
1124 fn test_transfer_owner_clause_only_matches() {
1125 use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::v0::DocumentTransferTransitionV0;
1126 use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::DocumentTransferTransition;
1127
1128 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1129 let contract = fixture.data_contract_owned();
1130
1131 let new_owner = Identifier::from([5u8; 32]);
1132
1133 let filter = DriveDocumentQueryFilter {
1135 contract: &contract,
1136 document_type_name: "niceDocument".to_string(),
1137 action_clauses: DocumentActionMatchClauses::Transfer {
1138 original_document_clauses: InternalClauses::default(),
1139 owner_clause: Some(ValueClause {
1140 operator: WhereOperator::Equal,
1141 value: new_owner.into(),
1142 }),
1143 },
1144 };
1145
1146 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1148 id: Identifier::from([3u8; 32]),
1149 document_type_name: "niceDocument".to_string(),
1150 data_contract_id: contract.id(),
1151 identity_contract_nonce: 0,
1152 token_payment_info: None,
1153 });
1154
1155 let transfer_v0 = DocumentTransferTransitionV0 {
1156 base: document_base.clone(),
1157 revision: 1_u64,
1158 recipient_owner_id: new_owner,
1159 };
1160 let transfer = DocumentTransition::Transfer(DocumentTransferTransition::V0(transfer_v0));
1161
1162 assert_eq!(
1164 filter.matches_document_transition(&transfer, None, PlatformVersion::latest()),
1165 TransitionCheckResult::Pass
1166 );
1167
1168 let other_owner = Identifier::from([6u8; 32]);
1170 let transfer_v0_mismatch = DocumentTransferTransitionV0 {
1171 base: document_base,
1172 revision: 1_u64,
1173 recipient_owner_id: other_owner,
1174 };
1175 let transfer_mismatch =
1176 DocumentTransition::Transfer(DocumentTransferTransition::V0(transfer_v0_mismatch));
1177 assert_eq!(
1178 filter.matches_document_transition(&transfer_mismatch, None, PlatformVersion::latest()),
1179 TransitionCheckResult::Fail
1180 );
1181 }
1182
1183 #[test]
1184 fn test_purchase_owner_clause_only_matches_and_requires_owner_context() {
1185 use dpp::fee::Credits;
1186 use dpp::state_transition::batch_transition::batched_transition::document_purchase_transition::v0::DocumentPurchaseTransitionV0;
1187 use dpp::state_transition::batch_transition::batched_transition::document_purchase_transition::DocumentPurchaseTransition;
1188
1189 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1190 let contract = fixture.data_contract_owned();
1191
1192 let purchaser = Identifier::from([7u8; 32]);
1193
1194 let filter = DriveDocumentQueryFilter {
1196 contract: &contract,
1197 document_type_name: "niceDocument".to_string(),
1198 action_clauses: DocumentActionMatchClauses::Purchase {
1199 original_document_clauses: InternalClauses::default(),
1200 owner_clause: Some(ValueClause {
1201 operator: WhereOperator::Equal,
1202 value: purchaser.into(),
1203 }),
1204 },
1205 };
1206
1207 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1209 id: Identifier::from([4u8; 32]),
1210 document_type_name: "niceDocument".to_string(),
1211 data_contract_id: contract.id(),
1212 identity_contract_nonce: 0,
1213 token_payment_info: None,
1214 });
1215
1216 let purchase_v0 = DocumentPurchaseTransitionV0 {
1217 base: document_base,
1218 revision: 1_u64,
1219 price: 10 as Credits,
1220 };
1221 let purchase = DocumentTransition::Purchase(DocumentPurchaseTransition::V0(purchase_v0));
1222
1223 assert_eq!(
1225 filter.matches_document_transition(&purchase, None, PlatformVersion::latest()),
1226 TransitionCheckResult::Fail
1227 );
1228 let owner_value = Value::Identifier(purchaser.to_buffer());
1230 assert_eq!(
1231 filter.matches_document_transition(
1232 &purchase,
1233 Some(&owner_value),
1234 PlatformVersion::latest()
1235 ),
1236 TransitionCheckResult::Pass
1237 );
1238 }
1239
1240 #[test]
1241 fn test_transfer_original_clause_only_matches_with_original_document() {
1242 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1243 let contract = fixture.data_contract_owned();
1244
1245 let mut eq = BTreeMap::new();
1247 eq.insert(
1248 "status".to_string(),
1249 WhereClause {
1250 field: "status".to_string(),
1251 operator: WhereOperator::Equal,
1252 value: Value::Text("active".to_string()),
1253 },
1254 );
1255 let filter = DriveDocumentQueryFilter {
1256 contract: &contract,
1257 document_type_name: "niceDocument".to_string(),
1258 action_clauses: DocumentActionMatchClauses::Transfer {
1259 original_document_clauses: InternalClauses {
1260 equal_clauses: eq,
1261 ..Default::default()
1262 },
1263 owner_clause: None,
1264 },
1265 };
1266
1267 let mut original = BTreeMap::new();
1269 original.insert("status".to_string(), Value::Text("active".to_string()));
1270 let original_doc = Document::V0(DocumentV0 {
1271 id: Identifier::from([9u8; 32]),
1272 owner_id: Identifier::from([0u8; 32]),
1273 properties: original,
1274 ..Default::default()
1275 });
1276 assert!(filter.matches_original_document(&original_doc));
1277
1278 }
1281
1282 #[test]
1283 fn test_delete_original_clause_only_matches_with_original_document() {
1284 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1285 let contract = fixture.data_contract_owned();
1286
1287 let mut eq = BTreeMap::new();
1289 eq.insert(
1290 "status".to_string(),
1291 WhereClause {
1292 field: "status".to_string(),
1293 operator: WhereOperator::Equal,
1294 value: Value::Text("active".to_string()),
1295 },
1296 );
1297 let filter = DriveDocumentQueryFilter {
1298 contract: &contract,
1299 document_type_name: "niceDocument".to_string(),
1300 action_clauses: DocumentActionMatchClauses::Delete {
1301 original_document_clauses: InternalClauses {
1302 equal_clauses: eq,
1303 ..Default::default()
1304 },
1305 },
1306 };
1307
1308 let mut original = BTreeMap::new();
1310 original.insert("status".to_string(), Value::Text("active".to_string()));
1311 let original_doc = Document::V0(DocumentV0 {
1312 id: Identifier::from([12u8; 32]),
1313 owner_id: Identifier::from([0u8; 32]),
1314 properties: original,
1315 ..Default::default()
1316 });
1317 assert!(filter.matches_original_document(&original_doc));
1318
1319 let mut original_bad = BTreeMap::new();
1324 original_bad.insert("status".to_string(), Value::Text("inactive".to_string()));
1325 let original_doc_bad = Document::V0(DocumentV0 {
1326 id: Identifier::from([12u8; 32]),
1327 owner_id: Identifier::from([0u8; 32]),
1328 properties: original_bad,
1329 ..Default::default()
1330 });
1331 assert!(!filter.matches_original_document(&original_doc_bad));
1332 }
1333
1334 #[test]
1335 fn test_update_price_price_clause_only_matches_and_with_original_clause() {
1336 use dpp::fee::Credits;
1337 use dpp::state_transition::batch_transition::batched_transition::document_update_price_transition::v0::DocumentUpdatePriceTransitionV0;
1338 use dpp::state_transition::batch_transition::batched_transition::document_update_price_transition::DocumentUpdatePriceTransition;
1339
1340 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1341 let contract = fixture.data_contract_owned();
1342
1343 let base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1344 id: Identifier::from([10u8; 32]),
1345 document_type_name: "niceDocument".to_string(),
1346 data_contract_id: contract.id(),
1347 identity_contract_nonce: 0,
1348 token_payment_info: None,
1349 });
1350
1351 let update_v0 = DocumentUpdatePriceTransitionV0 {
1353 base: base.clone(),
1354 revision: 1,
1355 price: 10 as Credits,
1356 };
1357 let update = DocumentTransition::UpdatePrice(DocumentUpdatePriceTransition::V0(update_v0));
1358
1359 let filter_price_only = DriveDocumentQueryFilter {
1360 contract: &contract,
1361 document_type_name: "niceDocument".to_string(),
1362 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1363 original_document_clauses: InternalClauses::default(),
1364 price_clause: Some(ValueClause {
1365 operator: WhereOperator::GreaterThan,
1366 value: Value::U64(5),
1367 }),
1368 },
1369 };
1370 assert_eq!(
1372 filter_price_only.matches_document_transition(&update, None, PlatformVersion::latest()),
1373 TransitionCheckResult::Pass
1374 );
1375
1376 let filter_price_only_fail = DriveDocumentQueryFilter {
1377 contract: &contract,
1378 document_type_name: "niceDocument".to_string(),
1379 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1380 original_document_clauses: InternalClauses::default(),
1381 price_clause: Some(ValueClause {
1382 operator: WhereOperator::GreaterThan,
1383 value: Value::U64(15),
1384 }),
1385 },
1386 };
1387 assert_eq!(
1388 filter_price_only_fail.matches_document_transition(
1389 &update,
1390 None,
1391 PlatformVersion::latest()
1392 ),
1393 TransitionCheckResult::Fail
1394 );
1395
1396 let mut eq = BTreeMap::new();
1398 eq.insert(
1399 "kind".to_string(),
1400 WhereClause {
1401 field: "kind".to_string(),
1402 operator: WhereOperator::Equal,
1403 value: Value::Text("sale".to_string()),
1404 },
1405 );
1406 let filter_with_orig = DriveDocumentQueryFilter {
1407 contract: &contract,
1408 document_type_name: "niceDocument".to_string(),
1409 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1410 original_document_clauses: InternalClauses {
1411 equal_clauses: eq,
1412 ..Default::default()
1413 },
1414 price_clause: Some(ValueClause {
1415 operator: WhereOperator::GreaterThanOrEquals,
1416 value: Value::U64(10),
1417 }),
1418 },
1419 };
1420 let mut original_doc = BTreeMap::new();
1421 original_doc.insert("kind".to_string(), Value::Text("sale".to_string()));
1422 assert_eq!(
1423 filter_with_orig.matches_document_transition(&update, None, PlatformVersion::latest()),
1424 TransitionCheckResult::NeedsOriginal
1425 );
1426 let original_document = Document::V0(DocumentV0 {
1427 id: Identifier::from([10u8; 32]),
1428 owner_id: Identifier::from([0u8; 32]),
1429 properties: original_doc,
1430 ..Default::default()
1431 });
1432 assert!(filter_with_orig.matches_original_document(&original_document));
1433
1434 }
1437
1438 #[test]
1439 fn test_replace_with_both_original_and_new_document_clauses() {
1440 use dpp::state_transition::batch_transition::batched_transition::document_replace_transition::v0::DocumentReplaceTransitionV0;
1441 use dpp::state_transition::batch_transition::batched_transition::document_replace_transition::DocumentReplaceTransition;
1442
1443 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1444 let contract = fixture.data_contract_owned();
1445
1446 let base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1447 id: Identifier::from([11u8; 32]),
1448 document_type_name: "niceDocument".to_string(),
1449 data_contract_id: contract.id(),
1450 identity_contract_nonce: 0,
1451 token_payment_info: None,
1452 });
1453
1454 let mut orig_eq = BTreeMap::new();
1456 orig_eq.insert(
1457 "status".to_string(),
1458 WhereClause {
1459 field: "status".to_string(),
1460 operator: WhereOperator::Equal,
1461 value: Value::Text("active".to_string()),
1462 },
1463 );
1464 let original_clauses = InternalClauses {
1465 equal_clauses: orig_eq,
1466 ..Default::default()
1467 };
1468
1469 let mut final_eq = BTreeMap::new();
1470 final_eq.insert(
1471 "score".to_string(),
1472 WhereClause {
1473 field: "score".to_string(),
1474 operator: WhereOperator::Equal,
1475 value: Value::U64(10),
1476 },
1477 );
1478 let new_document_clauses = InternalClauses {
1479 equal_clauses: final_eq,
1480 ..Default::default()
1481 };
1482
1483 let filter = DriveDocumentQueryFilter {
1484 contract: &contract,
1485 document_type_name: "niceDocument".to_string(),
1486 action_clauses: DocumentActionMatchClauses::Replace {
1487 original_document_clauses: original_clauses,
1488 new_document_clauses,
1489 },
1490 };
1491
1492 let mut data = BTreeMap::new();
1494 data.insert("score".to_string(), Value::U64(10));
1495 let replace_v0 = DocumentReplaceTransitionV0 {
1496 base,
1497 revision: 1,
1498 data,
1499 };
1500 let replace = DocumentTransition::Replace(DocumentReplaceTransition::V0(replace_v0));
1501
1502 let mut original_doc = BTreeMap::new();
1504 original_doc.insert("status".to_string(), Value::Text("active".to_string()));
1505 assert_eq!(
1506 filter.matches_document_transition(&replace, None, PlatformVersion::latest()),
1507 TransitionCheckResult::NeedsOriginal
1508 );
1509 let original_document = Document::V0(DocumentV0 {
1510 id: Identifier::from([11u8; 32]),
1511 owner_id: Identifier::from([0u8; 32]),
1512 properties: original_doc,
1513 ..Default::default()
1514 });
1515 assert!(filter.matches_original_document(&original_document));
1516
1517 let mut original_doc_bad = BTreeMap::new();
1522 original_doc_bad.insert("status".to_string(), Value::Text("inactive".to_string()));
1523 let original_document_bad = Document::V0(DocumentV0 {
1524 id: Identifier::from([11u8; 32]),
1525 owner_id: Identifier::from([0u8; 32]),
1526 properties: original_doc_bad,
1527 ..Default::default()
1528 });
1529 assert!(!filter.matches_original_document(&original_document_bad));
1530
1531 if let DocumentTransition::Replace(mut rep) = replace.clone() {
1533 let DocumentReplaceTransition::V0(ref mut v0) = rep;
1534 v0.data.insert("score".to_string(), Value::U64(9));
1535 let bad_final = DocumentTransition::Replace(rep);
1536 assert_eq!(
1537 filter.matches_document_transition(&bad_final, None, PlatformVersion::latest()),
1538 TransitionCheckResult::Fail
1539 );
1540 }
1541 }
1542
1543 fn generated_from_contract() -> DataContract {
1547 use dpp::data_contract::DataContractFactory;
1548 use dpp::platform_value::platform_value;
1549
1550 let generated = |source: &str| {
1551 platform_value!({
1552 "function": "sys.stringTransformations.homographSafeASCII",
1553 "params": [source]
1554 })
1555 };
1556 let documents = platform_value!({
1557 "handle": {
1558 "type": "object",
1559 "documentsMutable": true,
1560 "properties": {
1561 "label": { "type": "string", "maxLength": 32, "position": 0 },
1562 "normalizedLabel": {
1563 "type": "string",
1564 "maxLength": 32,
1565 "position": 1,
1566 "generatedFrom": generated("label")
1567 }
1568 },
1569 "additionalProperties": false
1570 },
1571 "entry": {
1572 "type": "object",
1573 "indexOnly": true,
1574 "documentsMutable": false,
1575 "indices": [
1576 {
1577 "name": "byNormalizedName",
1578 "properties": [{ "normalizedName": "asc" }, { "name": "asc" }],
1579 "terminal": "$ownerId"
1580 }
1581 ],
1582 "properties": {
1583 "name": { "type": "string", "maxLength": 32, "position": 0 },
1584 "normalizedName": {
1585 "type": "string",
1586 "maxLength": 32,
1587 "position": 1,
1588 "generatedFrom": generated("name")
1589 }
1590 },
1591 "required": ["name", "normalizedName"],
1592 "additionalProperties": false
1593 }
1594 });
1595 DataContractFactory::new(PlatformVersion::latest().protocol_version)
1596 .expect("factory")
1597 .create_with_value_config(Identifier::from([1u8; 32]), 1, documents, None, None)
1598 .expect("the contract parses")
1599 .data_contract_owned()
1600 }
1601
1602 #[test]
1606 fn test_matches_a_generated_property_the_transition_leaves_out() {
1607 use dpp::state_transition::batch_transition::batched_transition::document_create_transition::v0::DocumentCreateTransitionV0;
1608 use dpp::state_transition::batch_transition::batched_transition::document_create_transition::DocumentCreateTransition;
1609 use dpp::state_transition::batch_transition::batched_transition::document_index_only_delete_transition::v0::DocumentIndexOnlyDeleteTransitionV0;
1610 use dpp::state_transition::batch_transition::batched_transition::document_index_only_delete_transition::DocumentIndexOnlyDeleteTransition;
1611 use dpp::state_transition::batch_transition::batched_transition::document_replace_transition::v0::DocumentReplaceTransitionV0;
1612 use dpp::state_transition::batch_transition::batched_transition::document_replace_transition::DocumentReplaceTransition;
1613
1614 let contract = generated_from_contract();
1615 let platform_version = PlatformVersion::latest();
1616 let equal = |field: &str, value: &str| InternalClauses {
1617 equal_clauses: BTreeMap::from([(
1618 field.to_string(),
1619 WhereClause {
1620 field: field.to_string(),
1621 operator: WhereOperator::Equal,
1622 value: Value::Text(value.to_string()),
1623 },
1624 )]),
1625 ..Default::default()
1626 };
1627 let base = |document_type_name: &str| {
1628 DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1629 id: Identifier::from([3u8; 32]),
1630 document_type_name: document_type_name.to_string(),
1631 data_contract_id: contract.id(),
1632 identity_contract_nonce: 0,
1633 token_payment_info: None,
1634 })
1635 };
1636 let data = |key: &str, value: &str| {
1637 BTreeMap::from([(key.to_string(), Value::Text(value.to_string()))])
1638 };
1639
1640 let create =
1641 DocumentTransition::Create(DocumentCreateTransition::V0(DocumentCreateTransitionV0 {
1642 base: base("handle"),
1643 entropy: [0u8; 32],
1644 data: data("label", "Bob"),
1645 prefunded_voting_balance: None,
1646 }));
1647 let replace = DocumentTransition::Replace(DocumentReplaceTransition::V0(
1648 DocumentReplaceTransitionV0 {
1649 base: base("handle"),
1650 revision: 2,
1651 data: data("label", "Bob"),
1652 },
1653 ));
1654 let index_only_delete = DocumentTransition::IndexOnlyDelete(
1655 DocumentIndexOnlyDeleteTransition::V0(DocumentIndexOnlyDeleteTransitionV0 {
1656 base: base("entry"),
1657 data: data("name", "Bob"),
1658 }),
1659 );
1660
1661 for (normalized, expected) in [
1662 ("b0b", TransitionCheckResult::Pass),
1663 ("bob", TransitionCheckResult::Fail),
1664 ] {
1665 let create_filter = DriveDocumentQueryFilter {
1666 contract: &contract,
1667 document_type_name: "handle".to_string(),
1668 action_clauses: DocumentActionMatchClauses::Create {
1669 new_document_clauses: equal("normalizedLabel", normalized),
1670 },
1671 };
1672 assert_eq!(
1673 create_filter.matches_document_transition(&create, None, platform_version),
1674 expected,
1675 "create, normalizedLabel == {normalized:?}"
1676 );
1677
1678 let replace_filter = DriveDocumentQueryFilter {
1679 contract: &contract,
1680 document_type_name: "handle".to_string(),
1681 action_clauses: DocumentActionMatchClauses::Replace {
1682 original_document_clauses: InternalClauses::default(),
1683 new_document_clauses: equal("normalizedLabel", normalized),
1684 },
1685 };
1686 assert_eq!(
1687 replace_filter.matches_document_transition(&replace, None, platform_version),
1688 expected,
1689 "replace, normalizedLabel == {normalized:?}"
1690 );
1691
1692 let delete_filter = DriveDocumentQueryFilter {
1693 contract: &contract,
1694 document_type_name: "entry".to_string(),
1695 action_clauses: DocumentActionMatchClauses::Delete {
1696 original_document_clauses: equal("normalizedName", normalized),
1697 },
1698 };
1699 assert_eq!(
1700 delete_filter.matches_document_transition(
1701 &index_only_delete,
1702 None,
1703 platform_version
1704 ),
1705 expected,
1706 "indexOnly delete, normalizedName == {normalized:?}"
1707 );
1708 }
1709 }
1710
1711 #[test]
1712 fn test_matches_document_with_between_operator() {
1713 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1714 let contract = fixture.data_contract_owned();
1715
1716 let internal_clauses = InternalClauses {
1717 range_clause: Some(WhereClause {
1718 field: "value".to_string(),
1719 operator: WhereOperator::Between,
1720 value: Value::Array(vec![Value::U64(10), Value::U64(20)]),
1721 }),
1722 ..Default::default()
1723 };
1724
1725 let filter = DriveDocumentQueryFilter {
1726 contract: &contract,
1727 document_type_name: "niceDocument".to_string(),
1728 action_clauses: DocumentActionMatchClauses::Create {
1729 new_document_clauses: internal_clauses.clone(),
1730 },
1731 };
1732
1733 let document_base = DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
1734 id: Identifier::from([3u8; 32]),
1735 document_type_name: "niceDocument".to_string(),
1736 data_contract_id: contract.id(),
1737 identity_contract_nonce: 0,
1738 token_payment_info: None,
1739 });
1740
1741 let mut in_range = BTreeMap::new();
1743 in_range.insert("value".to_string(), Value::U64(15));
1744 let id_value: Value = document_base.id().into();
1745 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &in_range));
1746
1747 let mut lower_bound = BTreeMap::new();
1749 lower_bound.insert("value".to_string(), Value::U64(10));
1750 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &lower_bound));
1751
1752 let mut upper_bound = BTreeMap::new();
1754 upper_bound.insert("value".to_string(), Value::U64(20));
1755 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &upper_bound));
1756
1757 let mut below = BTreeMap::new();
1759 below.insert("value".to_string(), Value::U64(5));
1760 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &below));
1761
1762 let mut above = BTreeMap::new();
1764 above.insert("value".to_string(), Value::U64(25));
1765 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &above));
1766 }
1767
1768 #[test]
1769 fn test_validate_filter() {
1770 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1771 let contract = fixture.data_contract_owned();
1772
1773 let mut equal_clauses = BTreeMap::new();
1775 equal_clauses.insert(
1776 "firstName".to_string(),
1777 WhereClause {
1778 field: "firstName".to_string(),
1779 operator: WhereOperator::Equal,
1780 value: Value::Text("Alice".to_string()),
1781 },
1782 );
1783 let internal_clauses = InternalClauses {
1784 equal_clauses,
1785 ..Default::default()
1786 };
1787
1788 let valid_filter = DriveDocumentQueryFilter {
1789 contract: &contract,
1790 document_type_name: "indexedDocument".to_string(),
1791 action_clauses: DocumentActionMatchClauses::Create {
1792 new_document_clauses: internal_clauses,
1793 },
1794 };
1795
1796 assert!(
1797 valid_filter.validate().is_valid(),
1798 "Filter with indexed field should be valid"
1799 );
1800
1801 let mut equal_clauses = BTreeMap::new();
1804 equal_clauses.insert(
1805 "name".to_string(),
1806 WhereClause {
1807 field: "name".to_string(),
1808 operator: WhereOperator::Equal,
1809 value: Value::Text("value".to_string()),
1810 },
1811 );
1812 let internal_clauses = InternalClauses {
1813 equal_clauses,
1814 ..Default::default()
1815 };
1816
1817 let invalid_filter = DriveDocumentQueryFilter {
1818 contract: &contract,
1819 document_type_name: "niceDocument".to_string(),
1820 action_clauses: DocumentActionMatchClauses::Create {
1821 new_document_clauses: internal_clauses,
1822 },
1823 };
1824
1825 assert!(
1826 invalid_filter.validate().is_valid(),
1827 "Structural validate should ignore indexes"
1828 );
1829 let internal_clauses = InternalClauses {
1833 primary_key_equal_clause: Some(WhereClause {
1834 field: "$id".to_string(),
1835 operator: WhereOperator::Equal,
1836 value: Value::Identifier([42u8; 32]),
1837 }),
1838 ..Default::default()
1839 };
1840
1841 let primary_key_filter = DriveDocumentQueryFilter {
1842 contract: &contract,
1843 document_type_name: "indexedDocument".to_string(),
1844 action_clauses: DocumentActionMatchClauses::Create {
1845 new_document_clauses: internal_clauses,
1846 },
1847 };
1848
1849 assert!(
1850 primary_key_filter.validate().is_valid(),
1851 "Filter with only primary key should be valid"
1852 );
1853 }
1854
1855 #[test]
1856 fn test_validate_rejects_id_in_generic_clauses() {
1857 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1858 let contract = fixture.data_contract_owned();
1859
1860 let mut eq = BTreeMap::new();
1862 eq.insert(
1863 "$id".to_string(),
1864 WhereClause {
1865 field: "$id".to_string(),
1866 operator: WhereOperator::Equal,
1867 value: Value::Identifier([1u8; 32]),
1868 },
1869 );
1870 let filter = DriveDocumentQueryFilter {
1871 contract: &contract,
1872 document_type_name: "niceDocument".to_string(),
1873 action_clauses: DocumentActionMatchClauses::Create {
1874 new_document_clauses: InternalClauses {
1875 equal_clauses: eq,
1876 ..Default::default()
1877 },
1878 },
1879 };
1880 assert!(filter.validate().is_err());
1881
1882 let filter = DriveDocumentQueryFilter {
1884 contract: &contract,
1885 document_type_name: "niceDocument".to_string(),
1886 action_clauses: DocumentActionMatchClauses::Create {
1887 new_document_clauses: InternalClauses {
1888 range_clause: Some(WhereClause {
1889 field: "$id".to_string(),
1890 operator: WhereOperator::GreaterThan,
1891 value: Value::U64(0),
1892 }),
1893 ..Default::default()
1894 },
1895 },
1896 };
1897 assert!(filter.validate().is_err());
1898 }
1899
1900 #[test]
1901 fn test_validate_owner_and_price_clause_types() {
1902 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1903 let contract = fixture.data_contract_owned();
1904
1905 let filter = DriveDocumentQueryFilter {
1907 contract: &contract,
1908 document_type_name: "niceDocument".to_string(),
1909 action_clauses: DocumentActionMatchClauses::Transfer {
1910 original_document_clauses: InternalClauses::default(),
1911 owner_clause: Some(ValueClause {
1912 operator: WhereOperator::Equal,
1913 value: Value::Text("not-id".to_string()),
1914 }),
1915 },
1916 };
1917 assert!(filter.validate().is_err());
1918
1919 let filter = DriveDocumentQueryFilter {
1921 contract: &contract,
1922 document_type_name: "niceDocument".to_string(),
1923 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1924 original_document_clauses: InternalClauses::default(),
1925 price_clause: Some(ValueClause {
1926 operator: WhereOperator::Equal,
1927 value: Value::Float(1.23),
1928 }),
1929 },
1930 };
1931 assert!(filter.validate().is_err());
1932
1933 let filter = DriveDocumentQueryFilter {
1935 contract: &contract,
1936 document_type_name: "niceDocument".to_string(),
1937 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1938 original_document_clauses: InternalClauses::default(),
1939 price_clause: Some(ValueClause {
1940 operator: WhereOperator::Between,
1941 value: Value::Array(vec![Value::U64(1), Value::Float(2.0)]),
1942 }),
1943 },
1944 };
1945 assert!(filter.validate().is_err());
1946
1947 for operator in [
1949 WhereOperator::Between,
1950 WhereOperator::BetweenExcludeBounds,
1951 WhereOperator::BetweenExcludeLeft,
1952 WhereOperator::BetweenExcludeRight,
1953 ] {
1954 let filter = DriveDocumentQueryFilter {
1955 contract: &contract,
1956 document_type_name: "niceDocument".to_string(),
1957 action_clauses: DocumentActionMatchClauses::UpdatePrice {
1958 original_document_clauses: InternalClauses::default(),
1959 price_clause: Some(ValueClause {
1960 operator,
1961 value: Value::Array(vec![Value::U64(10), Value::U64(10)]),
1962 }),
1963 },
1964 };
1965 assert!(
1966 filter.validate().is_err(),
1967 "{operator:?} should reject equal price bounds"
1968 );
1969 }
1970 }
1971
1972 #[test]
1973 fn test_validate_startswith_on_numeric_field_rejected() {
1974 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1975 let contract = fixture.data_contract_owned();
1976
1977 let filter = DriveDocumentQueryFilter {
1979 contract: &contract,
1980 document_type_name: "niceDocument".to_string(),
1981 action_clauses: DocumentActionMatchClauses::Create {
1982 new_document_clauses: InternalClauses {
1983 range_clause: Some(WhereClause {
1984 field: "score".to_string(),
1985 operator: WhereOperator::StartsWith,
1986 value: Value::Text("1".to_string()),
1987 }),
1988 ..Default::default()
1989 },
1990 },
1991 };
1992 assert!(filter.validate().is_err());
1993 }
1994
1995 #[test]
1996 fn test_conversion_between_filter_and_query() {
1997 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
1998 let contract = fixture.data_contract_owned();
1999
2000 let internal_clauses = InternalClauses {
2001 primary_key_equal_clause: Some(WhereClause {
2002 field: "$id".to_string(),
2003 operator: WhereOperator::Equal,
2004 value: Value::Identifier([42u8; 32]),
2005 }),
2006 ..Default::default()
2007 };
2008
2009 let original_filter = DriveDocumentQueryFilter {
2010 contract: &contract,
2011 document_type_name: "niceDocument".to_string(),
2012 action_clauses: DocumentActionMatchClauses::Create {
2013 new_document_clauses: internal_clauses.clone(),
2014 },
2015 };
2016
2017 if let DocumentActionMatchClauses::Create {
2019 new_document_clauses,
2020 } = original_filter.action_clauses
2021 {
2022 assert_eq!(new_document_clauses, internal_clauses);
2023 } else {
2024 panic!("expected Create action clauses");
2025 }
2026 }
2027
2028 #[test]
2031 fn validate_rejects_unknown_document_type() {
2032 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2033 let contract = fixture.data_contract_owned();
2034
2035 let filter = DriveDocumentQueryFilter {
2036 contract: &contract,
2037 document_type_name: "doesNotExist".to_string(),
2038 action_clauses: DocumentActionMatchClauses::Create {
2039 new_document_clauses: InternalClauses::default(),
2040 },
2041 };
2042 let result = filter.validate();
2043 assert!(result.is_err());
2044 assert!(matches!(
2045 result.first_error(),
2046 Some(QuerySyntaxError::DocumentTypeNotFound(_))
2047 ));
2048 }
2049
2050 #[test]
2053 fn validate_transfer_owner_clause_in_with_identifiers_is_valid() {
2054 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2055 let contract = fixture.data_contract_owned();
2056
2057 let filter = DriveDocumentQueryFilter {
2058 contract: &contract,
2059 document_type_name: "niceDocument".to_string(),
2060 action_clauses: DocumentActionMatchClauses::Transfer {
2061 original_document_clauses: InternalClauses::default(),
2062 owner_clause: Some(ValueClause {
2063 operator: WhereOperator::In,
2064 value: Value::Array(vec![
2065 Value::Identifier([1u8; 32]),
2066 Value::Identifier([2u8; 32]),
2067 ]),
2068 }),
2069 },
2070 };
2071 assert!(filter.validate().is_valid());
2072 }
2073
2074 #[test]
2075 fn validate_transfer_owner_clause_in_with_non_identifiers_is_invalid() {
2076 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2077 let contract = fixture.data_contract_owned();
2078
2079 let filter = DriveDocumentQueryFilter {
2080 contract: &contract,
2081 document_type_name: "niceDocument".to_string(),
2082 action_clauses: DocumentActionMatchClauses::Transfer {
2083 original_document_clauses: InternalClauses::default(),
2084 owner_clause: Some(ValueClause {
2085 operator: WhereOperator::In,
2086 value: Value::Array(vec![Value::Text("not-an-id".to_string())]),
2087 }),
2088 },
2089 };
2090 assert!(filter.validate().is_err());
2091 }
2092
2093 #[test]
2094 fn validate_transfer_owner_clause_greater_than_is_invalid() {
2095 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2096 let contract = fixture.data_contract_owned();
2097
2098 let filter = DriveDocumentQueryFilter {
2099 contract: &contract,
2100 document_type_name: "niceDocument".to_string(),
2101 action_clauses: DocumentActionMatchClauses::Transfer {
2102 original_document_clauses: InternalClauses::default(),
2103 owner_clause: Some(ValueClause {
2104 operator: WhereOperator::GreaterThan,
2105 value: Value::Identifier([1u8; 32]),
2106 }),
2107 },
2108 };
2109 assert!(filter.validate().is_err());
2110 }
2111
2112 #[test]
2115 fn validate_purchase_owner_clause_in_with_identifiers_is_valid() {
2116 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2117 let contract = fixture.data_contract_owned();
2118
2119 let filter = DriveDocumentQueryFilter {
2120 contract: &contract,
2121 document_type_name: "niceDocument".to_string(),
2122 action_clauses: DocumentActionMatchClauses::Purchase {
2123 original_document_clauses: InternalClauses::default(),
2124 owner_clause: Some(ValueClause {
2125 operator: WhereOperator::In,
2126 value: Value::Array(vec![
2127 Value::Identifier([3u8; 32]),
2128 Value::Identifier([4u8; 32]),
2129 ]),
2130 }),
2131 },
2132 };
2133 assert!(filter.validate().is_valid());
2134 }
2135
2136 #[test]
2137 fn validate_purchase_owner_clause_non_identifier_is_invalid() {
2138 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2139 let contract = fixture.data_contract_owned();
2140
2141 let filter = DriveDocumentQueryFilter {
2142 contract: &contract,
2143 document_type_name: "niceDocument".to_string(),
2144 action_clauses: DocumentActionMatchClauses::Purchase {
2145 original_document_clauses: InternalClauses::default(),
2146 owner_clause: Some(ValueClause {
2147 operator: WhereOperator::Equal,
2148 value: Value::U64(42),
2149 }),
2150 },
2151 };
2152 assert!(filter.validate().is_err());
2153 }
2154
2155 #[test]
2156 fn validate_purchase_owner_clause_in_with_non_array_is_invalid() {
2157 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2158 let contract = fixture.data_contract_owned();
2159
2160 let filter = DriveDocumentQueryFilter {
2161 contract: &contract,
2162 document_type_name: "niceDocument".to_string(),
2163 action_clauses: DocumentActionMatchClauses::Purchase {
2164 original_document_clauses: InternalClauses::default(),
2165 owner_clause: Some(ValueClause {
2166 operator: WhereOperator::In,
2167 value: Value::Identifier([1u8; 32]),
2168 }),
2169 },
2170 };
2171 assert!(filter.validate().is_err());
2172 }
2173
2174 #[test]
2177 fn validate_price_clause_starts_with_is_invalid() {
2178 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2179 let contract = fixture.data_contract_owned();
2180
2181 let filter = DriveDocumentQueryFilter {
2182 contract: &contract,
2183 document_type_name: "niceDocument".to_string(),
2184 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2185 original_document_clauses: InternalClauses::default(),
2186 price_clause: Some(ValueClause {
2187 operator: WhereOperator::StartsWith,
2188 value: Value::Text("1".to_string()),
2189 }),
2190 },
2191 };
2192 assert!(filter.validate().is_err());
2193 }
2194
2195 #[test]
2196 fn validate_price_clause_in_with_integers_is_valid() {
2197 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2198 let contract = fixture.data_contract_owned();
2199
2200 let filter = DriveDocumentQueryFilter {
2201 contract: &contract,
2202 document_type_name: "niceDocument".to_string(),
2203 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2204 original_document_clauses: InternalClauses::default(),
2205 price_clause: Some(ValueClause {
2206 operator: WhereOperator::In,
2207 value: Value::Array(vec![Value::U64(10), Value::U64(20), Value::U64(30)]),
2208 }),
2209 },
2210 };
2211 assert!(filter.validate().is_valid());
2212 }
2213
2214 #[test]
2215 fn validate_price_clause_in_with_non_integer_is_invalid() {
2216 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2217 let contract = fixture.data_contract_owned();
2218
2219 let filter = DriveDocumentQueryFilter {
2220 contract: &contract,
2221 document_type_name: "niceDocument".to_string(),
2222 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2223 original_document_clauses: InternalClauses::default(),
2224 price_clause: Some(ValueClause {
2225 operator: WhereOperator::In,
2226 value: Value::Array(vec![Value::Text("not_int".to_string())]),
2227 }),
2228 },
2229 };
2230 assert!(filter.validate().is_err());
2231 }
2232
2233 #[test]
2234 fn validate_price_clause_in_with_non_array_is_invalid() {
2235 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2236 let contract = fixture.data_contract_owned();
2237
2238 let filter = DriveDocumentQueryFilter {
2239 contract: &contract,
2240 document_type_name: "niceDocument".to_string(),
2241 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2242 original_document_clauses: InternalClauses::default(),
2243 price_clause: Some(ValueClause {
2244 operator: WhereOperator::In,
2245 value: Value::U64(10),
2246 }),
2247 },
2248 };
2249 assert!(filter.validate().is_err());
2250 }
2251
2252 #[test]
2253 fn validate_price_clause_between_with_valid_integers_is_valid() {
2254 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2255 let contract = fixture.data_contract_owned();
2256
2257 let filter = DriveDocumentQueryFilter {
2258 contract: &contract,
2259 document_type_name: "niceDocument".to_string(),
2260 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2261 original_document_clauses: InternalClauses::default(),
2262 price_clause: Some(ValueClause {
2263 operator: WhereOperator::Between,
2264 value: Value::Array(vec![Value::U64(10), Value::U64(100)]),
2265 }),
2266 },
2267 };
2268 assert!(filter.validate().is_valid());
2269 }
2270
2271 #[test]
2272 fn validate_price_clause_between_with_descending_bounds_is_invalid() {
2273 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2274 let contract = fixture.data_contract_owned();
2275
2276 let filter = DriveDocumentQueryFilter {
2277 contract: &contract,
2278 document_type_name: "niceDocument".to_string(),
2279 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2280 original_document_clauses: InternalClauses::default(),
2281 price_clause: Some(ValueClause {
2282 operator: WhereOperator::Between,
2283 value: Value::Array(vec![Value::U64(100), Value::U64(10)]),
2284 }),
2285 },
2286 };
2287 assert!(filter.validate().is_err());
2288 }
2289
2290 #[test]
2291 fn validate_price_clause_between_with_non_array_is_invalid() {
2292 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2293 let contract = fixture.data_contract_owned();
2294
2295 let filter = DriveDocumentQueryFilter {
2296 contract: &contract,
2297 document_type_name: "niceDocument".to_string(),
2298 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2299 original_document_clauses: InternalClauses::default(),
2300 price_clause: Some(ValueClause {
2301 operator: WhereOperator::Between,
2302 value: Value::U64(50),
2303 }),
2304 },
2305 };
2306 assert!(filter.validate().is_err());
2307 }
2308
2309 #[test]
2310 fn validate_price_clause_less_than_with_integer_is_valid() {
2311 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2312 let contract = fixture.data_contract_owned();
2313
2314 let filter = DriveDocumentQueryFilter {
2315 contract: &contract,
2316 document_type_name: "niceDocument".to_string(),
2317 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2318 original_document_clauses: InternalClauses::default(),
2319 price_clause: Some(ValueClause {
2320 operator: WhereOperator::LessThan,
2321 value: Value::U64(1000),
2322 }),
2323 },
2324 };
2325 assert!(filter.validate().is_valid());
2326 }
2327
2328 #[test]
2329 fn validate_price_clause_less_than_or_equals_with_integer_is_valid() {
2330 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2331 let contract = fixture.data_contract_owned();
2332
2333 let filter = DriveDocumentQueryFilter {
2334 contract: &contract,
2335 document_type_name: "niceDocument".to_string(),
2336 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2337 original_document_clauses: InternalClauses::default(),
2338 price_clause: Some(ValueClause {
2339 operator: WhereOperator::LessThanOrEquals,
2340 value: Value::U64(500),
2341 }),
2342 },
2343 };
2344 assert!(filter.validate().is_valid());
2345 }
2346
2347 #[test]
2348 fn validate_price_clause_greater_than_or_equals_with_integer_is_valid() {
2349 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2350 let contract = fixture.data_contract_owned();
2351
2352 let filter = DriveDocumentQueryFilter {
2353 contract: &contract,
2354 document_type_name: "niceDocument".to_string(),
2355 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2356 original_document_clauses: InternalClauses::default(),
2357 price_clause: Some(ValueClause {
2358 operator: WhereOperator::GreaterThanOrEquals,
2359 value: Value::U64(50),
2360 }),
2361 },
2362 };
2363 assert!(filter.validate().is_valid());
2364 }
2365
2366 #[test]
2369 fn validate_delete_with_empty_clauses_is_valid() {
2370 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2371 let contract = fixture.data_contract_owned();
2372
2373 let filter = DriveDocumentQueryFilter {
2374 contract: &contract,
2375 document_type_name: "niceDocument".to_string(),
2376 action_clauses: DocumentActionMatchClauses::Delete {
2377 original_document_clauses: InternalClauses::default(),
2378 },
2379 };
2380 assert!(filter.validate().is_valid());
2381 }
2382
2383 #[test]
2384 fn validate_delete_with_primary_key_clause_is_valid() {
2385 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2386 let contract = fixture.data_contract_owned();
2387
2388 let filter = DriveDocumentQueryFilter {
2389 contract: &contract,
2390 document_type_name: "niceDocument".to_string(),
2391 action_clauses: DocumentActionMatchClauses::Delete {
2392 original_document_clauses: InternalClauses {
2393 primary_key_equal_clause: Some(WhereClause {
2394 field: "$id".to_string(),
2395 operator: WhereOperator::Equal,
2396 value: Value::Identifier([99u8; 32]),
2397 }),
2398 ..Default::default()
2399 },
2400 },
2401 };
2402 assert!(filter.validate().is_valid());
2403 }
2404
2405 #[test]
2408 fn matches_original_document_returns_false_for_create_action() {
2409 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2410 let contract = fixture.data_contract_owned();
2411
2412 let filter = DriveDocumentQueryFilter {
2413 contract: &contract,
2414 document_type_name: "niceDocument".to_string(),
2415 action_clauses: DocumentActionMatchClauses::Create {
2416 new_document_clauses: InternalClauses::default(),
2417 },
2418 };
2419
2420 let doc = Document::V0(DocumentV0 {
2421 id: Identifier::from([1u8; 32]),
2422 owner_id: Identifier::from([0u8; 32]),
2423 properties: BTreeMap::new(),
2424 ..Default::default()
2425 });
2426 assert!(!filter.matches_original_document(&doc));
2428 }
2429
2430 #[test]
2433 fn evaluate_clauses_primary_key_in_clause_matches() {
2434 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2435 let contract = fixture.data_contract_owned();
2436
2437 let target_id_1 = Identifier::from([10u8; 32]);
2438 let target_id_2 = Identifier::from([20u8; 32]);
2439
2440 let internal_clauses = InternalClauses {
2441 primary_key_in_clause: Some(WhereClause {
2442 field: "$id".to_string(),
2443 operator: WhereOperator::In,
2444 value: Value::Array(vec![target_id_1.into(), target_id_2.into()]),
2445 }),
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_1.into();
2459 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &BTreeMap::new()));
2460
2461 let other_id: Value = Identifier::from([99u8; 32]).into();
2463 assert!(!filter.evaluate_clauses(&internal_clauses, &other_id, &BTreeMap::new()));
2464 }
2465
2466 #[test]
2469 fn evaluate_clauses_primary_key_plus_equal_clause() {
2470 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2471 let contract = fixture.data_contract_owned();
2472
2473 let target_id = Identifier::from([50u8; 32]);
2474
2475 let mut equal_clauses = BTreeMap::new();
2476 equal_clauses.insert(
2477 "name".to_string(),
2478 WhereClause {
2479 field: "name".to_string(),
2480 operator: WhereOperator::Equal,
2481 value: Value::Text("test".to_string()),
2482 },
2483 );
2484
2485 let internal_clauses = InternalClauses {
2486 primary_key_equal_clause: Some(WhereClause {
2487 field: "$id".to_string(),
2488 operator: WhereOperator::Equal,
2489 value: target_id.into(),
2490 }),
2491 equal_clauses,
2492 ..Default::default()
2493 };
2494
2495 let filter = DriveDocumentQueryFilter {
2496 contract: &contract,
2497 document_type_name: "niceDocument".to_string(),
2498 action_clauses: DocumentActionMatchClauses::Create {
2499 new_document_clauses: internal_clauses.clone(),
2500 },
2501 };
2502
2503 let id_value: Value = target_id.into();
2505 let mut data = BTreeMap::new();
2506 data.insert("name".to_string(), Value::Text("test".to_string()));
2507 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &data));
2508
2509 let mut bad_data = BTreeMap::new();
2511 bad_data.insert("name".to_string(), Value::Text("other".to_string()));
2512 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &bad_data));
2513
2514 let wrong_id: Value = Identifier::from([99u8; 32]).into();
2516 assert!(!filter.evaluate_clauses(&internal_clauses, &wrong_id, &data));
2517 }
2518
2519 #[test]
2522 fn get_value_by_path_simple_key() {
2523 let mut root = BTreeMap::new();
2524 root.insert("name".to_string(), Value::Text("alice".to_string()));
2525 let result = get_value_by_path(&root, "name");
2526 assert_eq!(result, Some(&Value::Text("alice".to_string())));
2527 }
2528
2529 #[test]
2530 fn get_value_by_path_missing_key_returns_none() {
2531 let root = BTreeMap::new();
2532 let result = get_value_by_path(&root, "nonexistent");
2533 assert!(result.is_none());
2534 }
2535
2536 #[test]
2537 fn get_value_by_path_empty_path_returns_none() {
2538 let mut root = BTreeMap::new();
2539 root.insert("x".to_string(), Value::I64(1));
2540 let result = get_value_by_path(&root, "");
2541 assert!(result.is_none());
2542 }
2543
2544 #[test]
2545 fn get_value_by_path_nested_map() {
2546 let nested = vec![(
2547 Value::Text("level2".to_string()),
2548 Value::Text("deep_value".to_string()),
2549 )];
2550 let mut root = BTreeMap::new();
2551 root.insert("level1".to_string(), Value::Map(nested));
2552
2553 let result = get_value_by_path(&root, "level1.level2");
2554 assert_eq!(result, Some(&Value::Text("deep_value".to_string())));
2555 }
2556
2557 #[test]
2558 fn get_value_by_path_intermediate_non_map_returns_none() {
2559 let mut root = BTreeMap::new();
2560 root.insert("scalar".to_string(), Value::I64(42));
2561
2562 let result = get_value_by_path(&root, "scalar.child");
2564 assert!(result.is_none());
2565 }
2566
2567 #[test]
2568 fn get_value_by_path_deeply_nested() {
2569 let level3 = vec![(Value::Text("val".to_string()), Value::U64(999))];
2570 let level2 = vec![(Value::Text("c".to_string()), Value::Map(level3))];
2571 let mut root = BTreeMap::new();
2572 root.insert("a".to_string(), Value::Map(level2));
2573
2574 let result = get_value_by_path(&root, "a.c.val");
2575 assert_eq!(result, Some(&Value::U64(999)));
2576 }
2577
2578 #[test]
2579 fn get_value_by_path_missing_intermediate_key_returns_none() {
2580 let nested = vec![(Value::Text("exists".to_string()), Value::I64(1))];
2581 let mut root = BTreeMap::new();
2582 root.insert("a".to_string(), Value::Map(nested));
2583
2584 let result = get_value_by_path(&root, "a.not_here.val");
2585 assert!(result.is_none());
2586 }
2587
2588 #[test]
2591 fn evaluate_clauses_range_clause_missing_field_returns_false() {
2592 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2593 let contract = fixture.data_contract_owned();
2594
2595 let internal_clauses = InternalClauses {
2596 range_clause: Some(WhereClause {
2597 field: "nonexistent".to_string(),
2598 operator: WhereOperator::GreaterThan,
2599 value: Value::U64(0),
2600 }),
2601 ..Default::default()
2602 };
2603
2604 let filter = DriveDocumentQueryFilter {
2605 contract: &contract,
2606 document_type_name: "niceDocument".to_string(),
2607 action_clauses: DocumentActionMatchClauses::Create {
2608 new_document_clauses: internal_clauses.clone(),
2609 },
2610 };
2611
2612 let id_value: Value = Identifier::from([1u8; 32]).into();
2613 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &BTreeMap::new()));
2614 }
2615
2616 #[test]
2619 fn evaluate_clauses_in_clause_missing_field_returns_false() {
2620 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2621 let contract = fixture.data_contract_owned();
2622
2623 let internal_clauses = InternalClauses {
2624 in_clauses: vec![WhereClause {
2625 field: "nonexistent".to_string(),
2626 operator: WhereOperator::In,
2627 value: Value::Array(vec![Value::I64(1)]),
2628 }],
2629 ..Default::default()
2630 };
2631
2632 let filter = DriveDocumentQueryFilter {
2633 contract: &contract,
2634 document_type_name: "niceDocument".to_string(),
2635 action_clauses: DocumentActionMatchClauses::Create {
2636 new_document_clauses: internal_clauses.clone(),
2637 },
2638 };
2639
2640 let id_value: Value = Identifier::from([1u8; 32]).into();
2641 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &BTreeMap::new()));
2642 }
2643
2644 #[test]
2647 fn matches_original_document_update_price_evaluates_original_clauses() {
2648 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2649 let contract = fixture.data_contract_owned();
2650
2651 let mut eq = BTreeMap::new();
2652 eq.insert(
2653 "kind".to_string(),
2654 WhereClause {
2655 field: "kind".to_string(),
2656 operator: WhereOperator::Equal,
2657 value: Value::Text("premium".to_string()),
2658 },
2659 );
2660
2661 let filter = DriveDocumentQueryFilter {
2662 contract: &contract,
2663 document_type_name: "niceDocument".to_string(),
2664 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2665 original_document_clauses: InternalClauses {
2666 equal_clauses: eq,
2667 ..Default::default()
2668 },
2669 price_clause: None,
2670 },
2671 };
2672
2673 let mut props = BTreeMap::new();
2675 props.insert("kind".to_string(), Value::Text("premium".to_string()));
2676 let doc = Document::V0(DocumentV0 {
2677 id: Identifier::from([15u8; 32]),
2678 owner_id: Identifier::from([0u8; 32]),
2679 properties: props,
2680 ..Default::default()
2681 });
2682 assert!(filter.matches_original_document(&doc));
2683
2684 let mut bad_props = BTreeMap::new();
2686 bad_props.insert("kind".to_string(), Value::Text("basic".to_string()));
2687 let bad_doc = Document::V0(DocumentV0 {
2688 id: Identifier::from([15u8; 32]),
2689 owner_id: Identifier::from([0u8; 32]),
2690 properties: bad_props,
2691 ..Default::default()
2692 });
2693 assert!(!filter.matches_original_document(&bad_doc));
2694 }
2695
2696 #[test]
2699 fn matches_original_document_purchase_evaluates_original_clauses() {
2700 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2701 let contract = fixture.data_contract_owned();
2702
2703 let mut eq = BTreeMap::new();
2704 eq.insert(
2705 "status".to_string(),
2706 WhereClause {
2707 field: "status".to_string(),
2708 operator: WhereOperator::Equal,
2709 value: Value::Text("for_sale".to_string()),
2710 },
2711 );
2712
2713 let filter = DriveDocumentQueryFilter {
2714 contract: &contract,
2715 document_type_name: "niceDocument".to_string(),
2716 action_clauses: DocumentActionMatchClauses::Purchase {
2717 original_document_clauses: InternalClauses {
2718 equal_clauses: eq,
2719 ..Default::default()
2720 },
2721 owner_clause: None,
2722 },
2723 };
2724
2725 let mut props = BTreeMap::new();
2726 props.insert("status".to_string(), Value::Text("for_sale".to_string()));
2727 let doc = Document::V0(DocumentV0 {
2728 id: Identifier::from([20u8; 32]),
2729 owner_id: Identifier::from([0u8; 32]),
2730 properties: props,
2731 ..Default::default()
2732 });
2733 assert!(filter.matches_original_document(&doc));
2734 }
2735
2736 #[test]
2739 fn validate_replace_with_invalid_original_clauses_fails() {
2740 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2741 let contract = fixture.data_contract_owned();
2742
2743 let mut eq = BTreeMap::new();
2745 eq.insert(
2746 "nonexistentField".to_string(),
2747 WhereClause {
2748 field: "nonexistentField".to_string(),
2749 operator: WhereOperator::Equal,
2750 value: Value::I64(1),
2751 },
2752 );
2753
2754 let filter = DriveDocumentQueryFilter {
2755 contract: &contract,
2756 document_type_name: "niceDocument".to_string(),
2757 action_clauses: DocumentActionMatchClauses::Replace {
2758 original_document_clauses: InternalClauses {
2759 equal_clauses: eq,
2760 ..Default::default()
2761 },
2762 new_document_clauses: InternalClauses::default(),
2763 },
2764 };
2765 assert!(filter.validate().is_err());
2766 }
2767
2768 #[test]
2771 fn validate_transfer_with_invalid_original_clauses_fails() {
2772 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2773 let contract = fixture.data_contract_owned();
2774
2775 let mut eq = BTreeMap::new();
2776 eq.insert(
2777 "badField".to_string(),
2778 WhereClause {
2779 field: "badField".to_string(),
2780 operator: WhereOperator::Equal,
2781 value: Value::I64(1),
2782 },
2783 );
2784
2785 let filter = DriveDocumentQueryFilter {
2786 contract: &contract,
2787 document_type_name: "niceDocument".to_string(),
2788 action_clauses: DocumentActionMatchClauses::Transfer {
2789 original_document_clauses: InternalClauses {
2790 equal_clauses: eq,
2791 ..Default::default()
2792 },
2793 owner_clause: None,
2794 },
2795 };
2796 assert!(filter.validate().is_err());
2797 }
2798
2799 #[test]
2802 fn validate_update_price_with_invalid_original_clauses_fails() {
2803 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2804 let contract = fixture.data_contract_owned();
2805
2806 let mut eq = BTreeMap::new();
2807 eq.insert(
2808 "badField".to_string(),
2809 WhereClause {
2810 field: "badField".to_string(),
2811 operator: WhereOperator::Equal,
2812 value: Value::I64(1),
2813 },
2814 );
2815
2816 let filter = DriveDocumentQueryFilter {
2817 contract: &contract,
2818 document_type_name: "niceDocument".to_string(),
2819 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2820 original_document_clauses: InternalClauses {
2821 equal_clauses: eq,
2822 ..Default::default()
2823 },
2824 price_clause: None,
2825 },
2826 };
2827 assert!(filter.validate().is_err());
2828 }
2829
2830 #[test]
2833 fn validate_purchase_with_invalid_original_clauses_fails() {
2834 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2835 let contract = fixture.data_contract_owned();
2836
2837 let mut eq = BTreeMap::new();
2838 eq.insert(
2839 "badField".to_string(),
2840 WhereClause {
2841 field: "badField".to_string(),
2842 operator: WhereOperator::Equal,
2843 value: Value::I64(1),
2844 },
2845 );
2846
2847 let filter = DriveDocumentQueryFilter {
2848 contract: &contract,
2849 document_type_name: "niceDocument".to_string(),
2850 action_clauses: DocumentActionMatchClauses::Purchase {
2851 original_document_clauses: InternalClauses {
2852 equal_clauses: eq,
2853 ..Default::default()
2854 },
2855 owner_clause: None,
2856 },
2857 };
2858 assert!(filter.validate().is_err());
2859 }
2860
2861 #[test]
2864 fn evaluate_clauses_starts_with_range_clause() {
2865 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2866 let contract = fixture.data_contract_owned();
2867
2868 let internal_clauses = InternalClauses {
2869 range_clause: Some(WhereClause {
2870 field: "name".to_string(),
2871 operator: WhereOperator::StartsWith,
2872 value: Value::Text("Ali".to_string()),
2873 }),
2874 ..Default::default()
2875 };
2876
2877 let filter = DriveDocumentQueryFilter {
2878 contract: &contract,
2879 document_type_name: "niceDocument".to_string(),
2880 action_clauses: DocumentActionMatchClauses::Create {
2881 new_document_clauses: internal_clauses.clone(),
2882 },
2883 };
2884
2885 let id_value: Value = Identifier::from([1u8; 32]).into();
2886
2887 let mut matching = BTreeMap::new();
2888 matching.insert("name".to_string(), Value::Text("Alice".to_string()));
2889 assert!(filter.evaluate_clauses(&internal_clauses, &id_value, &matching));
2890
2891 let mut non_matching = BTreeMap::new();
2892 non_matching.insert("name".to_string(), Value::Text("Bob".to_string()));
2893 assert!(!filter.evaluate_clauses(&internal_clauses, &id_value, &non_matching));
2894 }
2895
2896 #[test]
2899 fn validate_price_clause_between_exclude_left_valid() {
2900 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2901 let contract = fixture.data_contract_owned();
2902
2903 let filter = DriveDocumentQueryFilter {
2904 contract: &contract,
2905 document_type_name: "niceDocument".to_string(),
2906 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2907 original_document_clauses: InternalClauses::default(),
2908 price_clause: Some(ValueClause {
2909 operator: WhereOperator::BetweenExcludeLeft,
2910 value: Value::Array(vec![Value::U64(5), Value::U64(50)]),
2911 }),
2912 },
2913 };
2914 assert!(filter.validate().is_valid());
2915 }
2916
2917 #[test]
2918 fn validate_price_clause_between_exclude_right_valid() {
2919 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2920 let contract = fixture.data_contract_owned();
2921
2922 let filter = DriveDocumentQueryFilter {
2923 contract: &contract,
2924 document_type_name: "niceDocument".to_string(),
2925 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2926 original_document_clauses: InternalClauses::default(),
2927 price_clause: Some(ValueClause {
2928 operator: WhereOperator::BetweenExcludeRight,
2929 value: Value::Array(vec![Value::U64(5), Value::U64(50)]),
2930 }),
2931 },
2932 };
2933 assert!(filter.validate().is_valid());
2934 }
2935
2936 #[test]
2937 fn validate_price_clause_between_exclude_bounds_valid() {
2938 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2939 let contract = fixture.data_contract_owned();
2940
2941 let filter = DriveDocumentQueryFilter {
2942 contract: &contract,
2943 document_type_name: "niceDocument".to_string(),
2944 action_clauses: DocumentActionMatchClauses::UpdatePrice {
2945 original_document_clauses: InternalClauses::default(),
2946 price_clause: Some(ValueClause {
2947 operator: WhereOperator::BetweenExcludeBounds,
2948 value: Value::Array(vec![Value::U64(5), Value::U64(50)]),
2949 }),
2950 },
2951 };
2952 assert!(filter.validate().is_valid());
2953 }
2954
2955 #[test]
2958 fn validate_transfer_owner_clause_in_with_non_array_is_invalid() {
2959 let fixture = get_data_contract_fixture(None, 0, LATEST_PLATFORM_VERSION.protocol_version);
2960 let contract = fixture.data_contract_owned();
2961
2962 let filter = DriveDocumentQueryFilter {
2963 contract: &contract,
2964 document_type_name: "niceDocument".to_string(),
2965 action_clauses: DocumentActionMatchClauses::Transfer {
2966 original_document_clauses: InternalClauses::default(),
2967 owner_clause: Some(ValueClause {
2968 operator: WhereOperator::In,
2969 value: Value::Identifier([1u8; 32]),
2970 }),
2971 },
2972 };
2973 assert!(filter.validate().is_err());
2974 }
2975
2976 mod typed_array_elements {
2979 use super::*;
2980 use crate::error::Error;
2981 use dpp::data_contract::DataContractFactory;
2982 use dpp::platform_value::platform_value;
2983 use dpp::platform_value::string_encoding::Encoding;
2984
2985 fn element_contract() -> DataContract {
2988 DataContractFactory::new(PlatformVersion::latest().protocol_version)
2989 .expect("factory")
2990 .create_with_value_config(
2991 Identifier::from([1; 32]),
2992 0,
2993 platform_value!({"post": {
2994 "type": "object",
2995 "properties": {
2996 "flags": {
2997 "type": "array", "maxItems": 4,
2998 "items": {"type": "boolean"}, "position": 0
2999 },
3000 "scores": {
3001 "type": "array", "maxItems": 8,
3002 "items": {"type": "integer", "minimum": 0, "maximum": 255},
3003 "position": 1
3004 },
3005 "hashtags": {
3006 "type": "array", "maxItems": 8,
3007 "items": {"type": "string", "maxLength": 61}, "position": 2
3008 },
3009 "pinned": {"type": "boolean", "position": 3},
3010 "language": {"type": "string", "maxLength": 2, "position": 4},
3011 "authors": {
3012 "type": "array", "maxItems": 4,
3013 "items": {
3014 "type": "array", "byteArray": true, "minItems": 32,
3015 "maxItems": 32,
3016 "contentMediaType": "application/x.dash.dpp.identifier"
3017 },
3018 "position": 5
3019 }
3020 },
3021 "additionalProperties": false
3022 }}),
3023 None,
3024 None,
3025 )
3026 .expect("contract")
3027 .data_contract_owned()
3028 }
3029
3030 fn clause(field: &str, operator: WhereOperator, value: Value) -> WhereClause {
3031 WhereClause {
3032 field: field.to_string(),
3033 operator,
3034 value,
3035 }
3036 }
3037
3038 fn filter_clauses(clause: WhereClause) -> InternalClauses {
3041 let mut clauses = InternalClauses::default();
3042 match clause.operator {
3043 WhereOperator::Equal | WhereOperator::Contains => {
3044 clauses.equal_clauses.insert(clause.field.clone(), clause);
3045 }
3046 _ => clauses.in_clauses.push(clause),
3047 }
3048 clauses
3049 }
3050
3051 fn create_filter(
3052 contract: &DataContract,
3053 clause: WhereClause,
3054 ) -> DriveDocumentQueryFilter<'_> {
3055 DriveDocumentQueryFilter {
3056 contract,
3057 document_type_name: "post".to_string(),
3058 action_clauses: DocumentActionMatchClauses::Create {
3059 new_document_clauses: filter_clauses(clause),
3060 },
3061 }
3062 }
3063
3064 fn filter_refusal(contract: &DataContract, clause: WhereClause) -> Option<String> {
3066 create_filter(contract, clause)
3067 .validate()
3068 .first_error()
3069 .map(ToString::to_string)
3070 }
3071
3072 fn query_refusal(contract: &DataContract, clause: &WhereClause) -> Option<String> {
3074 let document_type = contract.document_type_for_name("post").expect("post");
3075 clause
3076 .validate_array_element_clause(document_type, PlatformVersion::latest())
3077 .err()
3078 .map(|error| match error {
3079 Error::Query(error) => error.to_string(),
3080 other => other.to_string(),
3081 })
3082 }
3083
3084 fn post_data(property: &str, value: Value) -> BTreeMap<String, Value> {
3086 BTreeMap::from([(property.to_string(), value)])
3087 }
3088
3089 fn matches(
3092 contract: &DataContract,
3093 clause: WhereClause,
3094 document_data: BTreeMap<String, Value>,
3095 ) -> bool {
3096 let filter = create_filter(contract, clause);
3097 let DocumentActionMatchClauses::Create {
3098 new_document_clauses,
3099 } = &filter.action_clauses
3100 else {
3101 unreachable!("a create filter");
3102 };
3103 filter.evaluate_clauses(
3104 new_document_clauses,
3105 &Value::Identifier([3; 32]),
3106 &document_data,
3107 )
3108 }
3109
3110 #[test]
3111 fn should_admit_a_boolean_array_membership_as_documents_queries_do() {
3112 let contract = element_contract();
3113 for element_clause in [
3114 clause(
3115 "flags",
3116 WhereOperator::ContainsAny,
3117 Value::Array(vec![Value::Bool(true)]),
3118 ),
3119 clause("flags", WhereOperator::Contains, Value::Bool(false)),
3120 ] {
3121 assert_eq!(query_refusal(&contract, &element_clause), None);
3122 assert_eq!(filter_refusal(&contract, element_clause), None);
3123 }
3124 let flagged = post_data(
3125 "flags",
3126 Value::Array(vec![Value::Bool(false), Value::Bool(true)]),
3127 );
3128 let unflagged = post_data("flags", Value::Array(vec![Value::Bool(false)]));
3129 let any_true = clause(
3130 "flags",
3131 WhereOperator::ContainsAny,
3132 Value::Array(vec![Value::Bool(true)]),
3133 );
3134 assert!(matches(&contract, any_true.clone(), flagged));
3135 assert!(!matches(&contract, any_true, unflagged));
3136 }
3137
3138 #[test]
3139 fn should_keep_refusing_in_on_a_boolean_property() {
3140 let contract = element_contract();
3141 let pinned_in = clause(
3142 "pinned",
3143 WhereOperator::In,
3144 Value::Array(vec![Value::Bool(true)]),
3145 );
3146 assert_eq!(
3147 filter_refusal(&contract, pinned_in).as_deref(),
3148 Some("invalid where clause components error: operator not allowed for field type")
3149 );
3150 }
3151
3152 #[test]
3153 fn should_match_a_u8_array_against_a_list_given_as_bytes() {
3154 let contract = element_contract();
3155 let any_score = clause(
3156 "scores",
3157 WhereOperator::ContainsAny,
3158 Value::Bytes(vec![1, 2]),
3159 );
3160 assert_eq!(query_refusal(&contract, &any_score), None);
3161 assert_eq!(filter_refusal(&contract, any_score.clone()), None);
3162 let scored = post_data("scores", Value::Array(vec![Value::U8(1), Value::U8(5)]));
3163 let unscored = post_data("scores", Value::Array(vec![Value::U8(3)]));
3164 assert!(matches(&contract, any_score.clone(), scored));
3165 assert!(!matches(&contract, any_score, unscored));
3166 }
3167
3168 #[test]
3169 fn should_match_an_element_given_in_another_representation() {
3170 let contract = element_contract();
3171 let scored = post_data("scores", Value::Array(vec![Value::U8(1), Value::U8(5)]));
3173 assert!(matches(
3174 &contract,
3175 clause("scores", WhereOperator::Contains, Value::U64(5)),
3176 scored.clone()
3177 ));
3178 assert!(matches(
3179 &contract,
3180 clause(
3181 "scores",
3182 WhereOperator::ContainsAny,
3183 Value::Array(vec![Value::I64(9), Value::U16(1)])
3184 ),
3185 scored.clone()
3186 ));
3187 assert!(!matches(
3188 &contract,
3189 clause("scores", WhereOperator::Contains, Value::U64(9)),
3190 scored
3191 ));
3192 let author = [8u8; 32];
3195 let authored = post_data(
3196 "authors",
3197 Value::Array(vec![Value::Identifier(author), Value::Identifier([9; 32])]),
3198 );
3199 for given in [
3200 Value::Bytes(author.to_vec()),
3201 Value::Text(Identifier::from(author).to_string(Encoding::Base58)),
3202 Value::Identifier(author),
3203 ] {
3204 assert_eq!(
3205 query_refusal(
3206 &contract,
3207 &clause("authors", WhereOperator::Contains, given.clone())
3208 ),
3209 None
3210 );
3211 assert!(
3212 matches(
3213 &contract,
3214 clause("authors", WhereOperator::Contains, given.clone()),
3215 authored.clone()
3216 ),
3217 "{given:?}"
3218 );
3219 }
3220 }
3221
3222 #[test]
3223 fn should_refuse_what_documents_queries_refuse_with_the_same_error() {
3224 let contract = element_contract();
3225 for refused in [
3226 clause("hashtags", WhereOperator::Equal, Value::Text("dash".into())),
3227 clause(
3228 "language",
3229 WhereOperator::Contains,
3230 Value::Text("en".into()),
3231 ),
3232 clause("hashtags", WhereOperator::Contains, Value::U64(5)),
3233 clause("hashtags", WhereOperator::ContainsAny, Value::Array(vec![])),
3234 clause(
3235 "hashtags",
3236 WhereOperator::ContainsAny,
3237 Value::Array(vec![Value::Text("a".into()), Value::Text("a".into())]),
3238 ),
3239 ] {
3240 let query = query_refusal(&contract, &refused);
3241 assert!(query.is_some(), "a documents query refuses {refused:?}");
3242 assert_eq!(filter_refusal(&contract, refused), query);
3243 }
3244 }
3245 }
3246}