1use serde::Serialize;
68
69use crate::{
70 AWSDate, AWSDateTime, AWSEmail, AWSPhone, AWSTime, AWSTimestamp, AWSUrl, AppsyncError, ID,
71};
72
73pub trait IFSValueMarker: private::Sealed + Serialize {
75 fn to_value(&self) -> serde_json::Value {
77 serde_json::to_value(self).expect("cannot fail for IFSValueMarker types")
78 }
79}
80
81pub trait IFSBValueMarker: private::Sealed + Serialize {
83 fn to_value(&self) -> serde_json::Value {
85 serde_json::to_value(self).expect("cannot fail for IFSBValueMarker types")
86 }
87}
88
89mod private {
90 pub trait Sealed {}
91}
92
93macro_rules! impl_markers {
94 (nested $tr:ty, ($($t:ty),+)) => {
95 $(impl $tr for $t {})+
96 };
97 ($($tr:ty),+| $t:tt) => {
98 $(impl_markers!(nested $tr, $t);)+
99 }
100}
101impl_markers!(
102 IFSBValueMarker,
103 private::Sealed
104 | (
105 u8,
106 i8,
107 u16,
108 i16,
109 u32,
110 i32,
111 u64,
112 i64,
113 u128,
114 i128,
115 f32,
116 f64,
117 bool,
118 String,
119 &str,
120 ID,
121 AWSEmail,
122 AWSUrl,
123 AWSDate,
124 AWSTime,
125 AWSPhone,
126 AWSDateTime,
127 AWSTimestamp
128 )
129);
130impl_markers!(
131 IFSValueMarker
132 | (
133 u8,
134 i8,
135 u16,
136 i16,
137 u32,
138 i32,
139 u64,
140 i64,
141 u128,
142 i128,
143 f32,
144 f64,
145 String,
146 &str,
147 ID,
148 AWSEmail,
149 AWSUrl,
150 AWSDate,
151 AWSTime,
152 AWSPhone,
153 AWSDateTime,
154 AWSTimestamp
155 )
156);
157
158#[derive(Debug, Clone, PartialEq)]
160pub struct FixedVec<T, const N: usize>([Option<T>; N]);
161
162impl<T: Serialize, const N: usize> Serialize for FixedVec<T, N> {
163 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
164 where
165 S: serde::Serializer,
166 {
167 serializer.collect_seq(self.0.iter().flatten())
168 }
169}
170impl<T: IFSValueMarker, const N: usize> FixedVec<T, N> {
171 fn to_value(&self) -> serde_json::Value {
172 serde_json::to_value(self).expect("cannot fail for IFSValueMarker types")
173 }
174}
175
176type InVec<T> = FixedVec<T, 5>;
178
179type ContainsAnyVec<T> = FixedVec<T, 20>;
181
182macro_rules! impl_from_array {
183 (none 5) => {
184 [None, None, None, None, None]
185 };
186 (none 10) => {
187 [None, None, None, None, None, None, None, None, None, None]
188 };
189 (none 20) => {
190 [
191 None, None, None, None, None, None, None, None, None, None, None, None, None, None,
192 None, None, None, None, None, None,
193 ]
194 };
195 ($m:tt; $n:literal; $(($idx:tt, $v:ident)),*) => {
196 impl<T> From<[T; $n]> for FixedVec<T, $m> {
197 fn from([$($v),*]: [T; $n]) -> Self {
198 let mut slice = impl_from_array!(none $m);
199 $((slice[$idx]).replace($v);)*
200 Self(slice)
201 }
202 }
203 };
204}
205impl_from_array!(5; 1; (0, v1));
206impl_from_array!(5; 2; (0, v1), (1, v2));
207impl_from_array!(5; 3; (0, v1), (1, v2), (2, v3));
208impl_from_array!(5; 4; (0, v1), (1, v2), (2, v3), (3, v4));
209impl_from_array!(5; 5; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5));
210impl_from_array!(10; 1; (0, v1));
211impl_from_array!(10; 2; (0, v1), (1, v2));
212impl_from_array!(10; 3; (0, v1), (1, v2), (2, v3));
213impl_from_array!(10; 4; (0, v1), (1, v2), (2, v3), (3, v4));
214impl_from_array!(10; 5; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5));
215impl_from_array!(10; 6; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6));
216impl_from_array!(10; 7; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7));
217impl_from_array!(10; 8; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8));
218impl_from_array!(10; 9; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9));
219impl_from_array!(10; 10; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10));
220impl_from_array!(20; 1; (0, v1));
221impl_from_array!(20; 2; (0, v1), (1, v2));
222impl_from_array!(20; 3; (0, v1), (1, v2), (2, v3));
223impl_from_array!(20; 4; (0, v1), (1, v2), (2, v3), (3, v4));
224impl_from_array!(20; 5; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5));
225impl_from_array!(20; 6; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6));
226impl_from_array!(20; 7; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7));
227impl_from_array!(20; 8; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8));
228impl_from_array!(20; 9; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9));
229impl_from_array!(20; 10; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10));
230impl_from_array!(20; 11; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10), (10, v11));
231impl_from_array!(20; 12; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10), (10, v11), (11, v12));
232impl_from_array!(20; 13; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10), (10, v11), (11, v12), (12, v13));
233impl_from_array!(20; 14; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10), (10, v11), (11, v12), (12, v13), (13, v14));
234impl_from_array!(20; 15; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10), (10, v11), (11, v12), (12, v13), (13, v14), (14, v15));
235impl_from_array!(20; 16; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10), (10, v11), (11, v12), (12, v13), (13, v14), (14, v15), (15, v16));
236impl_from_array!(20; 17; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10), (10, v11), (11, v12), (12, v13), (13, v14), (14, v15), (15, v16), (16, v17));
237impl_from_array!(20; 18; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10), (10, v11), (11, v12), (12, v13), (13, v14), (14, v15), (15, v16), (16, v17), (17, v18));
238impl_from_array!(20; 19; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10), (10, v11), (11, v12), (12, v13), (13, v14), (14, v15), (15, v16), (16, v17), (17, v18), (18, v19));
239impl_from_array!(20; 20; (0, v1), (1, v2), (2, v3), (3, v4), (4, v5), (5, v6), (6, v7), (7, v8), (8, v9), (9, v10), (10, v11), (11, v12), (12, v13), (13, v14), (14, v15), (15, v16), (16, v17), (17, v18), (18, v19), (19, v20));
240
241#[derive(Debug, Clone, PartialEq, Serialize)]
243#[serde(transparent)]
244pub struct FieldPath(String);
245
246impl std::fmt::Display for FieldPath {
247 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248 write!(f, "{}", self.0)
249 }
250}
251
252impl FieldPath {
253 pub fn new(path: impl Into<String>) -> Result<Self, AppsyncError> {
270 let path = path.into();
271 if path.len() > 256 {
272 return Err(AppsyncError::new(
273 "ValidationError",
274 "Field path exceeds 256 characters",
275 ));
276 }
277 Ok(Self(path))
279 }
280
281 pub unsafe fn new_unchecked(path: impl Into<String>) -> Self {
295 Self(path.into())
296 }
297
298 pub fn eq<IFSB: IFSBValueMarker>(self, ifsb: IFSB) -> FieldFilter {
301 FieldFilter::new(self, ifsb.to_value(), FilterOp::Eq)
302 }
303
304 pub unsafe fn eq_unchecked<T: Serialize>(self, value: T) -> FieldFilter {
312 FieldFilter::new(self, serde_json::to_value(value).unwrap(), FilterOp::Eq)
313 }
314
315 pub fn ne<IFSB: IFSBValueMarker>(self, ifsb: IFSB) -> FieldFilter {
317 FieldFilter::new(self, ifsb.to_value(), FilterOp::Ne)
318 }
319
320 pub unsafe fn ne_unchecked<T: Serialize>(self, value: T) -> FieldFilter {
328 FieldFilter::new(self, serde_json::to_value(value).unwrap(), FilterOp::Ne)
329 }
330
331 pub fn le<IFS: IFSValueMarker>(self, ifs: IFS) -> FieldFilter {
334 FieldFilter::new(self, ifs.to_value(), FilterOp::Le)
335 }
336
337 pub unsafe fn le_unchecked<T: Serialize>(self, value: T) -> FieldFilter {
344 FieldFilter::new(self, serde_json::to_value(value).unwrap(), FilterOp::Le)
345 }
346
347 pub fn lt<IFS: IFSValueMarker>(self, ifs: IFS) -> FieldFilter {
349 FieldFilter::new(self, ifs.to_value(), FilterOp::Lt)
350 }
351
352 pub unsafe fn lt_unchecked<T: Serialize>(self, value: T) -> FieldFilter {
359 FieldFilter::new(self, serde_json::to_value(value).unwrap(), FilterOp::Lt)
360 }
361
362 pub fn ge<IFS: IFSValueMarker>(self, ifs: IFS) -> FieldFilter {
364 FieldFilter::new(self, ifs.to_value(), FilterOp::Ge)
365 }
366
367 pub unsafe fn ge_unchecked<T: Serialize>(self, value: T) -> FieldFilter {
374 FieldFilter::new(self, serde_json::to_value(value).unwrap(), FilterOp::Ge)
375 }
376
377 pub fn gt<IFS: IFSValueMarker>(self, ifs: IFS) -> FieldFilter {
379 FieldFilter::new(self, ifs.to_value(), FilterOp::Gt)
380 }
381
382 pub unsafe fn gt_unchecked<T: Serialize>(self, value: T) -> FieldFilter {
389 FieldFilter::new(self, serde_json::to_value(value).unwrap(), FilterOp::Gt)
390 }
391
392 pub fn contains<IFS: IFSValueMarker>(self, ifs: IFS) -> FieldFilter {
394 FieldFilter::new(self, ifs.to_value(), FilterOp::Contains)
395 }
396
397 pub unsafe fn contains_unchecked<T: Serialize>(self, value: T) -> FieldFilter {
404 FieldFilter::new(
405 self,
406 serde_json::to_value(value).unwrap(),
407 FilterOp::Contains,
408 )
409 }
410
411 pub fn not_contains<IFS: IFSValueMarker>(self, ifs: IFS) -> FieldFilter {
413 FieldFilter::new(self, ifs.to_value(), FilterOp::NotContains)
414 }
415
416 pub unsafe fn not_contains_unchecked<T: Serialize>(self, value: T) -> FieldFilter {
423 FieldFilter::new(
424 self,
425 serde_json::to_value(value).unwrap(),
426 FilterOp::NotContains,
427 )
428 }
429
430 pub fn begins_with(self, value: impl Into<String>) -> FieldFilter {
433 FieldFilter::new(
434 self,
435 serde_json::Value::String(value.into()),
436 FilterOp::BeginsWith,
437 )
438 }
439
440 pub fn in_values<IFS: IFSValueMarker>(self, values: impl Into<InVec<IFS>>) -> FieldFilter {
453 let in_vec = values.into();
454 FieldFilter::new(self, in_vec.to_value(), FilterOp::In)
455 }
456
457 pub unsafe fn in_values_unchecked<T: Serialize>(
464 self,
465 values: impl Into<InVec<T>>,
466 ) -> FieldFilter {
467 let in_vec = values.into();
468 FieldFilter::new(self, serde_json::to_value(in_vec).unwrap(), FilterOp::In)
469 }
470
471 pub fn not_in<IFS: IFSValueMarker>(self, values: impl Into<InVec<IFS>>) -> FieldFilter {
483 let in_vec = values.into();
484 FieldFilter::new(self, in_vec.to_value(), FilterOp::NotIn)
485 }
486
487 pub unsafe fn not_in_unchecked<T: Serialize>(self, values: impl Into<InVec<T>>) -> FieldFilter {
494 let in_vec = values.into();
495 FieldFilter::new(self, serde_json::to_value(in_vec).unwrap(), FilterOp::NotIn)
496 }
497
498 pub fn between<IFS: IFSValueMarker>(self, start: IFS, end: IFS) -> FieldFilter {
500 FieldFilter::new(
501 self,
502 FixedVec([Some(start), Some(end)]).to_value(),
503 FilterOp::Between,
504 )
505 }
506
507 pub unsafe fn between_unchecked<T: Serialize>(self, start: T, end: T) -> FieldFilter {
514 FieldFilter::new(
515 self,
516 serde_json::to_value(FixedVec([Some(start), Some(end)])).unwrap(),
517 FilterOp::Between,
518 )
519 }
520
521 pub fn contains_any<IFS: IFSValueMarker>(
533 self,
534 values: impl Into<ContainsAnyVec<IFS>>,
535 ) -> FieldFilter {
536 let contains_vec = values.into();
537 FieldFilter::new(self, contains_vec.to_value(), FilterOp::ContainsAny)
538 }
539
540 pub unsafe fn contains_any_unchecked<T: Serialize>(
547 self,
548 values: impl Into<ContainsAnyVec<T>>,
549 ) -> FieldFilter {
550 let contains_vec = values.into();
551 FieldFilter::new(
552 self,
553 serde_json::to_value(contains_vec).unwrap(),
554 FilterOp::ContainsAny,
555 )
556 }
557}
558
559#[derive(Debug, Clone, Serialize)]
560#[serde(rename_all = "camelCase")]
561enum FilterOp {
562 Eq,
563 Ne,
564 Le,
565 Lt,
566 Ge,
567 Gt,
568 Contains,
569 NotContains,
570 BeginsWith,
571 In,
572 NotIn,
573 Between,
574 ContainsAny,
575}
576
577#[derive(Debug, Clone, Serialize)]
592pub struct FieldFilter {
593 #[serde(rename = "fieldName")]
594 path: FieldPath,
595 operator: FilterOp,
596 value: serde_json::Value,
597}
598impl FieldFilter {
599 fn new(path: FieldPath, value: serde_json::Value, operator: FilterOp) -> Self {
600 Self {
601 path,
602 value,
603 operator,
604 }
605 }
606}
607#[derive(Debug, Clone, Serialize)]
624pub struct Filter {
625 filters: FixedVec<FieldFilter, 5>,
626}
627
628impl<T> From<T> for Filter
629where
630 T: Into<FixedVec<FieldFilter, 5>>,
631{
632 fn from(filters: T) -> Self {
633 Self {
634 filters: filters.into(),
635 }
636 }
637}
638
639impl From<FieldFilter> for Filter {
640 fn from(value: FieldFilter) -> Self {
641 Filter::from([value])
642 }
643}
644
645#[derive(Debug, Clone, Serialize)]
669pub struct FilterGroup {
670 #[serde(rename = "filterGroup")]
671 filters: FixedVec<Filter, 10>,
672}
673
674impl<T> From<T> for FilterGroup
675where
676 T: Into<FixedVec<Filter, 10>>,
677{
678 fn from(filters: T) -> Self {
679 Self {
680 filters: filters.into(),
681 }
682 }
683}
684
685impl From<FieldFilter> for FilterGroup {
686 fn from(value: FieldFilter) -> Self {
687 FilterGroup::from(Filter::from([value]))
688 }
689}
690impl From<Filter> for FilterGroup {
691 fn from(value: Filter) -> Self {
692 FilterGroup::from([value])
693 }
694}
695
696#[cfg(test)]
697mod tests {
698 use super::*;
699 use serde_json::json;
700
701 fn filter_value(f: &FieldFilter) -> serde_json::Value {
702 serde_json::to_value(f).unwrap()
703 }
704
705 #[test]
706 fn test_create_paths() {
707 let path = FieldPath::new("user.name").unwrap();
708 assert_eq!(path.to_string(), "user.name");
709
710 let path = FieldPath::new("nested.one.two.three.four.five");
711 assert!(path.is_ok());
712
713 let long_path = "a".repeat(257);
714 assert!(FieldPath::new(long_path).is_err());
715 }
716
717 #[test]
718 fn test_eq_operator() {
719 let filter = FieldPath::new("service").unwrap().eq("AWS AppSync");
721 assert_eq!(
722 filter_value(&filter),
723 json!({
724 "fieldName": "service",
725 "operator": "eq",
726 "value": "AWS AppSync"
727 })
728 );
729
730 let filter = FieldPath::new("severity").unwrap().eq(5);
732 assert_eq!(
733 filter_value(&filter),
734 json!({
735 "fieldName": "severity",
736 "operator": "eq",
737 "value": 5
738 })
739 );
740
741 let filter = FieldPath::new("enabled").unwrap().eq(true);
743 assert_eq!(
744 filter_value(&filter),
745 json!({
746 "fieldName": "enabled",
747 "operator": "eq",
748 "value": true
749 })
750 );
751 }
752
753 #[test]
754 fn test_ne_operator() {
755 let filter = FieldPath::new("service").unwrap().ne("AWS AppSync");
757 assert_eq!(
758 filter_value(&filter),
759 json!({
760 "fieldName": "service",
761 "operator": "ne",
762 "value": "AWS AppSync"
763 })
764 );
765
766 let filter = FieldPath::new("severity").unwrap().ne(5);
768 assert_eq!(
769 filter_value(&filter),
770 json!({
771 "fieldName": "severity",
772 "operator": "ne",
773 "value": 5
774 })
775 );
776
777 let filter = FieldPath::new("enabled").unwrap().ne(true);
779 assert_eq!(
780 filter_value(&filter),
781 json!({
782 "fieldName": "enabled",
783 "operator": "ne",
784 "value": true
785 })
786 );
787 }
788
789 #[test]
790 fn test_comparison_operators() {
791 let path = FieldPath::new("size").unwrap();
792
793 let filter = path.clone().le(5);
795 assert_eq!(
796 filter_value(&filter),
797 json!({
798 "fieldName": "size",
799 "operator": "le",
800 "value": 5
801 })
802 );
803
804 let filter = path.clone().lt(5);
806 assert_eq!(
807 filter_value(&filter),
808 json!({
809 "fieldName": "size",
810 "operator": "lt",
811 "value": 5
812 })
813 );
814
815 let filter = path.clone().ge(5);
817 assert_eq!(
818 filter_value(&filter),
819 json!({
820 "fieldName": "size",
821 "operator": "ge",
822 "value": 5
823 })
824 );
825
826 let filter = path.gt(5);
828 assert_eq!(
829 filter_value(&filter),
830 json!({
831 "fieldName": "size",
832 "operator": "gt",
833 "value": 5
834 })
835 );
836 }
837
838 #[test]
839 fn test_contains_operators() {
840 let path = FieldPath::new("seats").unwrap();
841
842 let filter = path.clone().contains(10);
844 assert_eq!(
845 filter_value(&filter),
846 json!({
847 "fieldName": "seats",
848 "operator": "contains",
849 "value": 10
850 })
851 );
852
853 let event_path = FieldPath::new("event").unwrap();
855 let filter = event_path.clone().contains("launch");
856 assert_eq!(
857 filter_value(&filter),
858 json!({
859 "fieldName": "event",
860 "operator": "contains",
861 "value": "launch"
862 })
863 );
864
865 let filter = path.not_contains(10);
867 assert_eq!(
868 filter_value(&filter),
869 json!({
870 "fieldName": "seats",
871 "operator": "notContains",
872 "value": 10
873 })
874 );
875 }
876
877 #[test]
878 fn test_begins_with() {
879 let filter = FieldPath::new("service").unwrap().begins_with("AWS");
880 assert_eq!(
881 filter_value(&filter),
882 json!({
883 "fieldName": "service",
884 "operator": "beginsWith",
885 "value": "AWS"
886 })
887 );
888 }
889
890 #[test]
891 fn test_in_operators() {
892 let path = FieldPath::new("severity").unwrap();
893
894 let filter = path.clone().in_values([1, 2, 3]);
896 assert_eq!(
897 filter_value(&filter),
898 json!({
899 "fieldName": "severity",
900 "operator": "in",
901 "value": [1, 2, 3]
902 })
903 );
904
905 let filter = path.not_in([1, 2, 3]);
907 assert_eq!(
908 filter_value(&filter),
909 json!({
910 "fieldName": "severity",
911 "operator": "notIn",
912 "value": [1, 2, 3]
913 })
914 );
915 }
916
917 #[test]
918 fn test_between() {
919 let filter = FieldPath::new("severity").unwrap().between(1, 5);
920 assert_eq!(
921 filter_value(&filter),
922 json!({
923 "fieldName": "severity",
924 "operator": "between",
925 "value": [1, 5]
926 })
927 );
928 }
929
930 #[test]
931 fn test_contains_any() {
932 let filter = FieldPath::new("seats").unwrap().contains_any([10, 15, 20]);
933 assert_eq!(
934 filter_value(&filter),
935 json!({
936 "fieldName": "seats",
937 "operator": "containsAny",
938 "value": [10, 15, 20]
939 })
940 );
941 }
942
943 #[test]
944 fn test_filter_group() {
945 let group = FilterGroup::from([Filter::from([
946 FieldPath::new("userId").unwrap().eq(1),
947 FieldPath::new("group")
948 .unwrap()
949 .in_values(["Admin", "Developer"]),
950 ])]);
951
952 assert_eq!(
953 serde_json::to_value(group).unwrap(),
954 json!({
955 "filterGroup": [
956 {
957 "filters": [
958 {
959 "fieldName": "userId",
960 "operator": "eq",
961 "value": 1
962 },
963 {
964 "fieldName": "group",
965 "operator": "in",
966 "value": ["Admin", "Developer"]
967 }
968 ]
969 }
970 ]
971 })
972 );
973 }
974
975 #[test]
976 fn test_complex_filter() {
977 let group = FilterGroup::from([
978 Filter::from([
979 FieldPath::new("severity").unwrap().le(3),
980 FieldPath::new("type").unwrap().eq("error"),
981 ]),
982 Filter::from([
983 FieldPath::new("service").unwrap().begins_with("AWS"),
984 FieldPath::new("region")
985 .unwrap()
986 .in_values(["us-east-1", "eu-west-1"]),
987 ]),
988 ]);
989
990 assert_eq!(
991 serde_json::to_value(group).unwrap(),
992 json!({
993 "filterGroup": [
994 {
995 "filters": [
996 {
997 "fieldName": "severity",
998 "operator": "le",
999 "value": 3
1000 },
1001 {
1002 "fieldName": "type",
1003 "operator": "eq",
1004 "value": "error"
1005 }
1006 ]
1007 },
1008 {
1009 "filters": [
1010 {
1011 "fieldName": "service",
1012 "operator": "beginsWith",
1013 "value": "AWS"
1014 },
1015 {
1016 "fieldName": "region",
1017 "operator": "in",
1018 "value": ["us-east-1", "eu-west-1"]
1019 }
1020 ]
1021 }
1022 ]
1023 })
1024 );
1025 }
1026}