Skip to content

Commit 6bdb547

Browse files
Price Floors: Max Rules and Max Dimensions (#3630)
1 parent ba33fac commit 6bdb547

18 files changed

+702
-68
lines changed

src/main/java/org/prebid/server/floors/BasicPriceFloorProcessor.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,25 @@ private PriceFloorRules resolveFloors(Account account, BidRequest bidRequest, Li
135135

136136
if (requestFloors != null) {
137137
try {
138-
PriceFloorRulesValidator.validateRules(requestFloors, Integer.MAX_VALUE);
138+
final Optional<AccountPriceFloorsConfig> priceFloorsConfig = Optional.ofNullable(account)
139+
.map(Account::getAuction)
140+
.map(AccountAuctionConfig::getPriceFloors);
141+
142+
final Long maxRules = priceFloorsConfig.map(AccountPriceFloorsConfig::getMaxRules)
143+
.orElse(null);
144+
final Long maxDimensions = priceFloorsConfig.map(AccountPriceFloorsConfig::getMaxSchemaDims)
145+
.orElse(null);
146+
147+
PriceFloorRulesValidator.validateRules(
148+
requestFloors,
149+
PriceFloorsConfigResolver.resolveMaxValue(maxRules),
150+
PriceFloorsConfigResolver.resolveMaxValue(maxDimensions));
151+
139152
return createFloorsFrom(requestFloors, fetchStatus, PriceFloorLocation.request);
140153
} catch (PreBidException e) {
141-
errors.add("Failed to parse price floors from request, with a reason : %s ".formatted(e.getMessage()));
154+
errors.add("Failed to parse price floors from request, with a reason: %s".formatted(e.getMessage()));
142155
conditionalLogger.error(
143-
"Failed to parse price floors from request with id: '%s', with a reason : %s "
156+
"Failed to parse price floors from request with id: '%s', with a reason: %s"
144157
.formatted(bidRequest.getId(), e.getMessage()),
145158
0.01d);
146159
}

src/main/java/org/prebid/server/floors/PriceFloorFetcher.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,10 @@ private ResponseCacheInfo parseFloorResponse(HttpClientResponse httpClientRespon
176176
}
177177

178178
final PriceFloorData priceFloorData = parsePriceFloorData(body, accountId);
179-
PriceFloorRulesValidator.validateRulesData(priceFloorData, resolveMaxRules(fetchConfig.getMaxRules()));
179+
PriceFloorRulesValidator.validateRulesData(
180+
priceFloorData,
181+
PriceFloorsConfigResolver.resolveMaxValue(fetchConfig.getMaxRules()),
182+
PriceFloorsConfigResolver.resolveMaxValue(fetchConfig.getMaxSchemaDims()));
180183

181184
return ResponseCacheInfo.of(priceFloorData,
182185
FetchStatus.success,
@@ -194,12 +197,6 @@ private PriceFloorData parsePriceFloorData(String body, String accountId) {
194197
return priceFloorData;
195198
}
196199

197-
private static int resolveMaxRules(Long accountMaxRules) {
198-
return accountMaxRules != null && !accountMaxRules.equals(0L)
199-
? Math.toIntExact(accountMaxRules)
200-
: Integer.MAX_VALUE;
201-
}
202-
203200
private Long cacheTtlFromResponse(HttpClientResponse httpClientResponse, String fetchUrl) {
204201
final String cacheControlValue = httpClientResponse.getHeaders().get(HttpHeaders.CACHE_CONTROL);
205202
final Matcher cacheHeaderMatcher = StringUtils.isNotBlank(cacheControlValue)

src/main/java/org/prebid/server/floors/PriceFloorRulesValidator.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
import org.apache.commons.collections4.MapUtils;
55
import org.prebid.server.exception.PreBidException;
66
import org.prebid.server.floors.model.PriceFloorData;
7+
import org.prebid.server.floors.model.PriceFloorField;
78
import org.prebid.server.floors.model.PriceFloorModelGroup;
89
import org.prebid.server.floors.model.PriceFloorRules;
10+
import org.prebid.server.floors.model.PriceFloorSchema;
911

1012
import java.math.BigDecimal;
13+
import java.util.List;
1114
import java.util.Map;
1215
import java.util.Objects;
16+
import java.util.Optional;
1317

1418
public class PriceFloorRulesValidator {
1519

@@ -23,7 +27,7 @@ public class PriceFloorRulesValidator {
2327
private PriceFloorRulesValidator() {
2428
}
2529

26-
public static void validateRules(PriceFloorRules priceFloorRules, Integer maxRules) {
30+
public static void validateRules(PriceFloorRules priceFloorRules, Integer maxRules, Integer maxDimensions) {
2731

2832
final Integer rootSkipRate = priceFloorRules.getSkipRate();
2933
if (rootSkipRate != null && (rootSkipRate < SKIP_RATE_MIN || rootSkipRate > SKIP_RATE_MAX)) {
@@ -36,10 +40,10 @@ public static void validateRules(PriceFloorRules priceFloorRules, Integer maxRul
3640
throw new PreBidException("Price floor floorMin must be positive float, but was " + floorMin);
3741
}
3842

39-
validateRulesData(priceFloorRules.getData(), maxRules);
43+
validateRulesData(priceFloorRules.getData(), maxRules, maxDimensions);
4044
}
4145

42-
public static void validateRulesData(PriceFloorData priceFloorData, Integer maxRules) {
46+
public static void validateRulesData(PriceFloorData priceFloorData, Integer maxRules, Integer maxDimensions) {
4347
if (priceFloorData == null) {
4448
throw new PreBidException("Price floor rules data must be present");
4549
}
@@ -64,10 +68,10 @@ public static void validateRulesData(PriceFloorData priceFloorData, Integer maxR
6468

6569
priceFloorData.getModelGroups().stream()
6670
.filter(Objects::nonNull)
67-
.forEach(modelGroup -> validateModelGroup(modelGroup, maxRules));
71+
.forEach(modelGroup -> validateModelGroup(modelGroup, maxRules, maxDimensions));
6872
}
6973

70-
private static void validateModelGroup(PriceFloorModelGroup modelGroup, Integer maxRules) {
74+
private static void validateModelGroup(PriceFloorModelGroup modelGroup, Integer maxRules, Integer maxDimensions) {
7175
final Integer modelWeight = modelGroup.getModelWeight();
7276
if (modelWeight != null
7377
&& (modelWeight < MODEL_WEIGHT_MIN_VALUE || modelWeight > MODEL_WEIGHT_MAX_VALUE)) {
@@ -95,8 +99,21 @@ private static void validateModelGroup(PriceFloorModelGroup modelGroup, Integer
9599
}
96100

97101
if (maxRules != null && values.size() > maxRules) {
98-
throw new PreBidException(
99-
"Price floor rules number %s exceeded its maximum number %s".formatted(values.size(), maxRules));
102+
throw new PreBidException("Price floor rules number %s exceeded its maximum number %s"
103+
.formatted(values.size(), maxRules));
104+
}
105+
106+
final List<PriceFloorField> fields = Optional.ofNullable(modelGroup.getSchema())
107+
.map(PriceFloorSchema::getFields)
108+
.orElse(null);
109+
110+
if (CollectionUtils.isEmpty(fields)) {
111+
throw new PreBidException("Price floor dimensions can't be null or empty, but were " + fields);
112+
}
113+
114+
if (maxDimensions != null && fields.size() > maxDimensions) {
115+
throw new PreBidException("Price floor schema dimensions %s exceeded its maximum number %s"
116+
.formatted(fields.size(), maxDimensions));
100117
}
101118
}
102119
}

src/main/java/org/prebid/server/floors/PriceFloorsConfigResolver.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ public class PriceFloorsConfigResolver {
2323
private static final ConditionalLogger conditionalLogger = new ConditionalLogger(logger);
2424

2525
private static final int MIN_MAX_AGE_SEC_VALUE = 600;
26+
private static final int MAX_AGE_SEC_VALUE = Integer.MAX_VALUE;
2627
private static final int MIN_PERIODIC_SEC_VALUE = 300;
2728
private static final int MIN_TIMEOUT_MS_VALUE = 10;
2829
private static final int MAX_TIMEOUT_MS_VALUE = 10_000;
2930
private static final int MIN_RULES_VALUE = 0;
30-
private static final int MIN_FILE_SIZE_VALUE = 0;
31-
private static final int MAX_AGE_SEC_VALUE = Integer.MAX_VALUE;
3231
private static final int MAX_RULES_VALUE = Integer.MAX_VALUE;
32+
private static final int MIN_DIMENSIONS_VALUE = 0;
33+
private static final int MAX_DIMENSIONS_VALUE = 19;
34+
private static final int MIN_FILE_SIZE_VALUE = 0;
3335
private static final int MAX_FILE_SIZE_VALUE = Integer.MAX_VALUE;
3436
private static final int MIN_ENFORCE_RATE_VALUE = 0;
3537
private static final int MAX_ENFORCE_RATE_VALUE = 100;
@@ -71,6 +73,16 @@ private static void validatePriceFloorConfig(Account account) {
7173
throw new PreBidException(invalidPriceFloorsPropertyMessage("enforce-floors-rate", enforceRate));
7274
}
7375

76+
final Long maxRules = floorsConfig.getMaxRules();
77+
if (maxRules != null && isNotInRange(maxRules, MIN_RULES_VALUE, MAX_RULES_VALUE)) {
78+
throw new PreBidException(invalidPriceFloorsPropertyMessage("max-rules", maxRules));
79+
}
80+
81+
final Long maxDimensions = floorsConfig.getMaxSchemaDims();
82+
if (maxDimensions != null && isNotInRange(maxDimensions, MIN_DIMENSIONS_VALUE, MAX_DIMENSIONS_VALUE)) {
83+
throw new PreBidException(invalidPriceFloorsPropertyMessage("max-schema-dimensions", maxDimensions));
84+
}
85+
7486
final AccountPriceFloorsFetchConfig fetchConfig =
7587
ObjectUtil.getIfNotNull(floorsConfig, AccountPriceFloorsConfig::getFetch);
7688

@@ -108,6 +120,11 @@ private static void validatePriceFloorsFetchConfig(AccountPriceFloorsFetchConfig
108120
throw new PreBidException(invalidPriceFloorsPropertyMessage("max-rules", maxRules));
109121
}
110122

123+
final Long maxDimensions = fetchConfig.getMaxSchemaDims();
124+
if (maxDimensions != null && isNotInRange(maxDimensions, MIN_DIMENSIONS_VALUE, MAX_DIMENSIONS_VALUE)) {
125+
throw new PreBidException(invalidPriceFloorsPropertyMessage("max-schema-dimensions", maxDimensions));
126+
}
127+
111128
final Long maxFileSize = fetchConfig.getMaxFileSizeKb();
112129
if (maxFileSize != null && isNotInRange(maxFileSize, MIN_FILE_SIZE_VALUE, MAX_FILE_SIZE_VALUE)) {
113130
throw new PreBidException(invalidPriceFloorsPropertyMessage("max-file-size-kb", maxFileSize));
@@ -121,4 +138,8 @@ private static boolean isNotInRange(long number, long min, long max) {
121138
private static String invalidPriceFloorsPropertyMessage(String property, Object value) {
122139
return "Invalid price-floors property '%s', value passed: %s".formatted(property, value);
123140
}
141+
142+
public static int resolveMaxValue(Long value) {
143+
return value != null && !value.equals(0L) ? Math.toIntExact(value) : Integer.MAX_VALUE;
144+
}
124145
}

src/main/java/org/prebid/server/settings/model/AccountPriceFloorsConfig.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,10 @@ public class AccountPriceFloorsConfig {
2323

2424
@JsonAlias("use-dynamic-data")
2525
Boolean useDynamicData;
26+
27+
@JsonAlias("max-rules")
28+
Long maxRules;
29+
30+
@JsonAlias("max-schema-dims")
31+
Long maxSchemaDims;
2632
}

src/main/java/org/prebid/server/settings/model/AccountPriceFloorsFetchConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public class AccountPriceFloorsFetchConfig {
2121
@JsonAlias("max-rules")
2222
Long maxRules;
2323

24+
@JsonAlias("max-schema-dims")
25+
Long maxSchemaDims;
26+
2427
@JsonAlias("max-age-sec")
2528
Long maxAgeSec;
2629

src/main/resources/application.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,17 @@ settings:
174174
"enabled": false,
175175
"timeout-ms": 5000,
176176
"max-rules": 0,
177+
"max-schema-dims": 5,
177178
"max-file-size-kb": 200,
178179
"max-age-sec": 86400,
179180
"period-sec": 3600
180181
},
181182
"enforce-floors-rate": 100,
182183
"adjust-for-bid-adjustment": true,
183184
"enforce-deal-floors": true,
184-
"use-dynamic-data": true
185+
"use-dynamic-data": true,
186+
"max-rules": 100,
187+
"max-schema-dims": 3
185188
}
186189
}
187190
}

src/test/groovy/org/prebid/server/functional/model/config/AccountPriceFloorsConfig.groovy

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class AccountPriceFloorsConfig {
1515
Boolean adjustForBidAdjustment
1616
Boolean enforceDealFloors
1717
Boolean useDynamicData
18+
Long maxRules
19+
Long maxSchemaDims
1820

1921
@JsonProperty("enforce_floors_rate")
2022
Integer enforceFloorsRateSnakeCase
@@ -24,4 +26,8 @@ class AccountPriceFloorsConfig {
2426
Boolean enforceDealFloorsSnakeCase
2527
@JsonProperty("use_dynamic_data")
2628
Boolean useDynamicDataSnakeCase
29+
@JsonProperty("max_rules")
30+
Long maxRulesSnakeCase
31+
@JsonProperty("max_schema_dims")
32+
Long maxSchemaDimsSnakeCase
2733
}

src/test/groovy/org/prebid/server/functional/model/config/PriceFloorsFetch.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ class PriceFloorsFetch {
2626
Integer periodSec
2727
@JsonProperty("period_sec")
2828
Integer periodSecSnakeCase
29+
Integer maxSchemaDims
30+
@JsonProperty("max_schema_dims")
31+
Integer maxSchemaDimsSnakeCase
2932
}

src/test/groovy/org/prebid/server/functional/model/request/auction/ExtPrebidFloors.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class ExtPrebidFloors {
2020
ExtPrebidPriceFloorEnforcement enforcement
2121
Integer skipRate
2222
PriceFloorData data
23+
Long maxSchemaDims
2324

2425
static ExtPrebidFloors getExtPrebidFloors() {
2526
new ExtPrebidFloors(floorMin: FLOOR_MIN,

0 commit comments

Comments
 (0)