Example: PreOperation Recipes¶
A collection of practical PreOperation patterns for common real-world transformations.
Shard / bucket routing¶
Route traffic to a specific shard using ModuloPreOperation:
// userId % 8 == 3 → shard 3 of 8
IncludedPredicate.builder()
.lhs("$.userId")
.preOperation(ModuloPreOperation.builder().rhs(8).build())
.detail(EqualityDetail.builder().values(Sets.newHashSet(3)).build())
.build()
Percentage rollout (25% of users):
// userId % 100 < 25
IncludedPredicate.builder()
.lhs("$.userId")
.preOperation(ModuloPreOperation.builder().rhs(100).build())
.detail(RangeDetail.builder()
.lowerBound(0).includeLowerBound(true)
.upperBound(25).includeUpperBound(false)
.build())
.build()
Country code from locale string¶
Extract the country suffix from "en-IN", "zh-CN", "pt-BR":
// "en-IN" → substring[3,5) → "IN"
IncludedPredicate.builder()
.lhs("$.locale")
.preOperation(SubStringPreOperation.builder().beginIndex(3).endIndex(5).build())
.detail(EqualityDetail.builder().values(Sets.newHashSet("IN", "SG")).build())
.build()
Phone number ISD code¶
Match the first 3 characters of a phone number:
// "+91xxxxxxxxxx" → "+91"
IncludedPredicate.builder()
.lhs("$.phone")
.preOperation(SubStringPreOperation.builder().beginIndex(0).endIndex(3).build())
.detail(EqualityDetail.builder().values(Sets.newHashSet("+91")).build())
.build()
Time-of-day gating¶
Use DateTimePreOperation to restrict a rule to specific hours:
// Peak hours: 10am–2pm
IncludedPredicate.builder()
.lhs("$.eventEpochMs")
.preOperation(DateTimePreOperation.builder()
.extractionType(DateExtractionType.HOUR_OF_DAY)
.build())
.detail(RangeDetail.builder()
.lowerBound(10).includeLowerBound(true)
.upperBound(14).includeUpperBound(false)
.build())
.build()
Day-of-week gating (weekdays only, 2=Monday … 6=Friday):
IncludedPredicate.builder()
.lhs("$.eventEpochMs")
.preOperation(DateTimePreOperation.builder()
.extractionType(DateExtractionType.DAY_OF_WEEK)
.build())
.detail(RangeDetail.builder()
.lowerBound(2).includeLowerBound(true)
.upperBound(6).includeUpperBound(true)
.build())
.build()
Collection size check¶
Fire a rule only when a user has at least 3 items in cart:
IncludedPredicate.builder()
.lhs("$.cartItems") // a JSON array
.preOperation(SizePreOperation.builder().build())
.detail(RangeDetail.builder()
.lowerBound(3).includeLowerBound(true)
.build())
.build()
String length gate¶
Only process short usernames (≤ 15 chars):
IncludedPredicate.builder()
.lhs("$.username")
.preOperation(LengthPreOperation.builder().build())
.detail(RangeDetail.builder()
.upperBound(15).includeUpperBound(true)
.build())
.build()
Chained operations: extract year from a date field then check¶
// requestDate is ISO string like "2024-03-15T10:00:00Z"
// Chain: parse string → epoch ms → extract year
IncludedPredicate.builder()
.lhs("$.requestDate")
.preOperations(List.of(
ToDateTimePreOperation.builder().build(), // "2024-03-15T..." → epoch ms
DateTimePreOperation.builder()
.extractionType(DateExtractionType.YEAR)
.build() // epoch ms → 2024
))
.detail(EqualityDetail.builder()
.values(Sets.newHashSet(2024, 2025))
.build())
.build()
Binary feature flag from a bitmask¶
Check whether bit 3 is set in a feature flags integer:
// flags & 8 == 8 → bit 3 is set
// Approach: flags % 16 >= 8 (checks bits 3 downward)
// For a clean power-of-2 bit check, use division + modulo chain:
// Extract bit 3: (flags / 8) % 2 == 1
IncludedPredicate.builder()
.lhs("$.featureFlags")
.preOperations(List.of(
DivisionPreOperation.builder().rhs(8.0).build(), // shift right by 3
ModuloPreOperation.builder().rhs(2).build() // isolate bit
))
.detail(EqualityDetail.builder().values(Sets.newHashSet(1)).build())
.build()
Percentage-of-value gate¶
Trigger a rule when a discount is more than 20% of the original price:
// discountPct = (discount / originalPrice) * 100 > 20
// Use two separate predicates and let the conjunction handle the AND:
// Predicate 1: originalPrice > 0 (guard)
// Predicate 2: discountPct > 20
IncludedPredicate.builder()
.lhs("$.discountPct")
.detail(RangeDetail.builder()
.lowerBound(20).includeLowerBound(false)
.build())
.build()
Or with a PreOperation if the event only has raw values:
// discount * 100 / originalPrice — use in conjunction with a static range check
// Note: for ratio calculations, pre-compute in your event producer for cleaner rules