Skip to content

Commit c2a86cf

Browse files
authored
Nativo: Add optional placementId parameter and Prebid Renderer in response (#4380)
1 parent 6e71d88 commit c2a86cf

15 files changed

Lines changed: 767 additions & 31 deletions

File tree

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package org.prebid.server.bidder.nativo;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
5+
import com.iab.openrtb.request.BidRequest;
6+
import com.iab.openrtb.request.Imp;
7+
import com.iab.openrtb.response.Bid;
8+
import com.iab.openrtb.response.BidResponse;
9+
import com.iab.openrtb.response.SeatBid;
10+
import org.apache.commons.collections4.CollectionUtils;
11+
import org.prebid.server.bidder.Bidder;
12+
import org.prebid.server.bidder.model.BidderBid;
13+
import org.prebid.server.bidder.model.BidderCall;
14+
import org.prebid.server.bidder.model.BidderError;
15+
import org.prebid.server.bidder.model.HttpRequest;
16+
import org.prebid.server.bidder.model.Result;
17+
import org.prebid.server.exception.PreBidException;
18+
import org.prebid.server.json.DecodeException;
19+
import org.prebid.server.json.JacksonMapper;
20+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
21+
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
22+
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
23+
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidSdk;
24+
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidSdkRenderer;
25+
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
26+
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidMeta;
27+
import org.prebid.server.util.BidderUtil;
28+
import org.prebid.server.util.HttpUtil;
29+
30+
import java.util.ArrayList;
31+
import java.util.Collection;
32+
import java.util.Collections;
33+
import java.util.List;
34+
import java.util.Map;
35+
import java.util.Objects;
36+
import java.util.Optional;
37+
import java.util.function.Function;
38+
import java.util.stream.Collectors;
39+
40+
public class NativoBidder implements Bidder<BidRequest> {
41+
42+
private static final String NATIVO_RENDERER_NAME = "NativoRenderer";
43+
private static final String PREBID_EXT = "prebid";
44+
private static final TypeReference<ExtPrebid<ExtBidPrebid, ?>> EXT_PREBID_TYPE_REFERENCE =
45+
new TypeReference<>() {
46+
};
47+
48+
private final String endpointUrl;
49+
private final JacksonMapper mapper;
50+
51+
public NativoBidder(String endpointUrl, JacksonMapper mapper) {
52+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
53+
this.mapper = Objects.requireNonNull(mapper);
54+
}
55+
56+
@Override
57+
public final Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
58+
return Result.withValue(BidderUtil.defaultRequest(bidRequest, endpointUrl, mapper));
59+
}
60+
61+
@Override
62+
public final Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
63+
try {
64+
final List<BidderError> errors = new ArrayList<>();
65+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
66+
return Result.of(extractBids(httpCall.getRequest().getPayload(), bidResponse, errors), errors);
67+
} catch (DecodeException e) {
68+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
69+
}
70+
}
71+
72+
private List<BidderBid> extractBids(BidRequest bidRequest, BidResponse bidResponse, List<BidderError> errors) {
73+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
74+
return Collections.emptyList();
75+
}
76+
return bidsFromResponse(bidRequest, bidResponse, errors);
77+
}
78+
79+
private List<BidderBid> bidsFromResponse(BidRequest bidRequest, BidResponse bidResponse, List<BidderError> errors) {
80+
final Map<String, Imp> impMap = bidRequest.getImp().stream()
81+
.collect(Collectors.toMap(Imp::getId, Function.identity()));
82+
final String rendererVersion = getNativoRendererVersion(bidRequest);
83+
84+
return bidResponse.getSeatbid().stream()
85+
.filter(Objects::nonNull)
86+
.map(SeatBid::getBid)
87+
.filter(Objects::nonNull)
88+
.flatMap(Collection::stream)
89+
.filter(Objects::nonNull)
90+
.map(bid -> updateBid(bid, rendererVersion, errors))
91+
.map(bid -> BidderBid.of(bid, BidderUtil.getBidType(bid, impMap), bidResponse.getCur()))
92+
.toList();
93+
}
94+
95+
private String getNativoRendererVersion(BidRequest bidRequest) {
96+
return Optional.ofNullable(bidRequest.getExt())
97+
.map(ExtRequest::getPrebid)
98+
.map(ExtRequestPrebid::getSdk)
99+
.map(ExtRequestPrebidSdk::getRenderers)
100+
.orElse(Collections.emptyList())
101+
.stream()
102+
.filter(renderer -> NATIVO_RENDERER_NAME.equals(renderer.getName()))
103+
.map(ExtRequestPrebidSdkRenderer::getVersion)
104+
.findFirst()
105+
.orElse(null);
106+
}
107+
108+
private Bid updateBid(Bid bid, String rendererVersion, List<BidderError> errors) {
109+
if (rendererVersion == null) {
110+
return bid;
111+
}
112+
113+
final ObjectNode updateBidExt;
114+
try {
115+
updateBidExt = setRendererToResponse(bid, rendererVersion);
116+
} catch (PreBidException e) {
117+
errors.add(BidderError.badServerResponse(e.getMessage()));
118+
return bid;
119+
}
120+
121+
return bid.toBuilder().ext(updateBidExt).build();
122+
}
123+
124+
private ObjectNode setRendererToResponse(Bid bid, String rendererVersion) {
125+
final ObjectNode bidExt = bid.getExt();
126+
final Optional<ExtBidPrebid> extBidPrebid = Optional.ofNullable(bidExt)
127+
.map(ext -> parseExtBidPrebid(bidExt, bid.getId()));
128+
129+
final ExtBidPrebidMeta updatedMeta = extBidPrebid
130+
.map(ExtBidPrebid::getMeta)
131+
.map(ExtBidPrebidMeta::toBuilder)
132+
.orElseGet(ExtBidPrebidMeta::builder)
133+
.rendererName(NATIVO_RENDERER_NAME)
134+
.rendererVersion(rendererVersion)
135+
.build();
136+
137+
final ExtBidPrebid modifiedExtBidPrebid = extBidPrebid
138+
.map(ExtBidPrebid::toBuilder)
139+
.orElseGet(ExtBidPrebid::builder)
140+
.meta(updatedMeta)
141+
.build();
142+
143+
final ObjectNode updatedBidExt = bidExt != null ? bidExt : mapper.mapper().createObjectNode();
144+
updatedBidExt.set(PREBID_EXT, mapper.mapper().valueToTree(modifiedExtBidPrebid));
145+
return updatedBidExt;
146+
}
147+
148+
private ExtBidPrebid parseExtBidPrebid(ObjectNode bidExt, String bidId) {
149+
try {
150+
return mapper.mapper().convertValue(bidExt, EXT_PREBID_TYPE_REFERENCE).getPrebid();
151+
} catch (IllegalArgumentException e) {
152+
throw new PreBidException("Invalid ext passed in bid with id: " + bidId);
153+
}
154+
}
155+
156+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.prebid.server.proto.openrtb.ext.request.nativo;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpNativo {
8+
9+
@JsonProperty("placementId")
10+
Object placementId;
11+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.prebid.server.spring.config.bidder;
2+
3+
import org.prebid.server.bidder.BidderDeps;
4+
import org.prebid.server.bidder.nativo.NativoBidder;
5+
import org.prebid.server.json.JacksonMapper;
6+
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
7+
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
8+
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
9+
import org.prebid.server.spring.env.YamlPropertySourceFactory;
10+
import org.springframework.beans.factory.annotation.Value;
11+
import org.springframework.boot.context.properties.ConfigurationProperties;
12+
import org.springframework.context.annotation.Bean;
13+
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.context.annotation.PropertySource;
15+
16+
import jakarta.validation.constraints.NotBlank;
17+
18+
@Configuration
19+
@PropertySource(value = "classpath:/bidder-config/nativo.yaml", factory = YamlPropertySourceFactory.class)
20+
public class NativoConfiguration {
21+
22+
private static final String BIDDER_NAME = "nativo";
23+
24+
@Bean("nativoConfigurationProperties")
25+
@ConfigurationProperties("adapters.nativo")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps nativoBidderDeps(BidderConfigurationProperties nativoConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(nativoConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new NativoBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}

src/main/resources/bidder-config/generic.yaml

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -97,28 +97,6 @@ adapters:
9797
url: https://prebid.cwi.re/v1/usersync?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&rd={{redirect_url}}
9898
support-cors: false
9999
uid-macro: '$UID'
100-
nativo:
101-
enabled: false
102-
endpoint: https://exchange.postrelease.com/esi?ntv_epid=7
103-
pbs-enforces-ccpa: false
104-
meta-info:
105-
maintainer-email: prebiddev@nativo.com
106-
app-media-types:
107-
- banner
108-
- video
109-
- native
110-
site-media-types:
111-
- banner
112-
- video
113-
- native
114-
supported-vendors:
115-
vendor-id: 263
116-
usersync:
117-
cookie-family-name: nativo
118-
redirect:
119-
url: https://jadserve.postrelease.com/suid/101787?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&ntv_gpp_consent={{gpp}}&ntv_r={{redirect_url}}
120-
support-cors: false
121-
uid-macro: 'NTV_USER_ID'
122100
meta-info:
123101
maintainer-email: maintainer@example.com
124102
app-media-types:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
adapters:
2+
nativo:
3+
endpoint: https://exchange.postrelease.com/esi?ntv_epid=7
4+
ortb-version: "2.6"
5+
pbs-enforces-ccpa: false
6+
meta-info:
7+
maintainer-email: prebiddev@nativo.com
8+
app-media-types:
9+
- banner
10+
- video
11+
- native
12+
site-media-types:
13+
- banner
14+
- video
15+
- native
16+
supported-vendors:
17+
vendor-id: 263
18+
usersync:
19+
cookie-family-name: nativo
20+
redirect:
21+
url: https://jadserve.postrelease.com/suid/101787?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&ntv_gpp_consent={{gpp}}&ntv_r={{redirect_url}}
22+
support-cors: false
23+
uid-macro: 'NTV_USER_ID'
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Nativo Adapter Params",
4+
"description": "A schema which validates params accepted by the Nativo adapter",
5+
"type": "object",
6+
"properties": {
7+
"placementId": {
8+
"type": ["integer", "string"],
9+
"description": "Placement ID"
10+
}
11+
}
12+
}

src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,6 @@ LIMIT 1
123123
"adapters.generic.aliases.cwire.meta-info.app-media-types" : "",
124124
"adapters.generic.aliases.blue.meta-info.app-media-types" : "",
125125
"adapters.generic.aliases.blue.meta-info.site-media-types" : "",
126-
"adapters.generic.aliases.nativo.meta-info.app-media-types" : "",
127-
"adapters.generic.aliases.nativo.meta-info.site-media-types" : "",
128126
"adapters.generic.aliases.infytv.meta-info.app-media-types" : "",
129127
"adapters.generic.aliases.infytv.meta-info.site-media-types" : "",
130128
"adapters.generic.aliases.zeta-global-ssp.meta-info.app-media-types" : "",

src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,8 +1348,8 @@ class BidderParamsSpec extends BaseSpec {
13481348
def bidRequest = BidRequest.defaultBidRequest.tap {
13491349
imp[0].ext.prebid.bidder.tap {
13501350
it.generic.exampleProperty = PBSUtils.randomNumber
1351-
//Nativo hard coded bidder alias in generic.yaml
1352-
it.nativo = new Generic(exampleProperty: PBSUtils.randomNumber)
1351+
//Adrino hard coded bidder alias in generic.yaml
1352+
it.adrino = new Adrino(hash: PBSUtils.randomNumber)
13531353
}
13541354
}
13551355

@@ -1362,9 +1362,9 @@ class BidderParamsSpec extends BaseSpec {
13621362
["WARNING: request.imp[0].ext.prebid.bidder.generic was dropped with a reason: " +
13631363
"request.imp[0].ext.prebid.bidder.generic failed validation.\n" +
13641364
"\$.exampleProperty: integer found, string expected",
1365-
"WARNING: request.imp[0].ext.prebid.bidder.nativo was dropped with a reason: " +
1366-
"request.imp[0].ext.prebid.bidder.nativo failed validation.\n" +
1367-
"\$.exampleProperty: integer found, string expected",
1365+
"WARNING: request.imp[0].ext.prebid.bidder.adrino was dropped with a reason: " +
1366+
"request.imp[0].ext.prebid.bidder.adrino failed validation.\n" +
1367+
"\$.hash: integer found, string expected",
13681368
"WARNING: request.imp[0].ext must contain at least one valid bidder"]
13691369

13701370
and: "PBS should not call bidder"

0 commit comments

Comments
 (0)