Nativo: Add optional placementId parameter and SDK Renderer Version in response#4380
Open
rafataveira wants to merge 3 commits intoprebid:masterfrom
Open
Nativo: Add optional placementId parameter and SDK Renderer Version in response#4380rafataveira wants to merge 3 commits intoprebid:masterfrom
rafataveira wants to merge 3 commits intoprebid:masterfrom
Conversation
osulzhenko
reviewed
Feb 19, 2026
src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy
Show resolved
Hide resolved
CTMBNara
requested changes
Feb 19, 2026
Collaborator
There was a problem hiding this comment.
I see that in GO version this adapter supports Ortb2.6
Comment on lines
6
to
8
| /** | ||
| * Defines the contract for bidrequest.imp[i].ext.nativo | ||
| */ |
Comment on lines
43
to
44
| private static final TypeReference<ExtPrebid<ExtBidPrebid, ObjectNode>> EXT_PREBID_TYPE_REFERENCE = | ||
| new TypeReference<>() { }; |
Collaborator
There was a problem hiding this comment.
private static final TypeReference<ExtPrebid<ExtBidPrebid, ?>> EXT_PREBID_TYPE_REFERENCE =
new TypeReference<>() {
};
Comment on lines
77
to
155
| private List<BidderBid> bidsFromResponse(BidRequest bidRequest, BidResponse bidResponse, List<BidderError> errors) { | ||
| final Map<String, Imp> impMap = bidRequest.getImp().stream() | ||
| .collect(Collectors.toMap(Imp::getId, Function.identity())); | ||
| return bidResponse.getSeatbid().stream() | ||
| .filter(Objects::nonNull) | ||
| .map(seatBid -> seatBid.getBid().stream() | ||
| .filter(Objects::nonNull) | ||
| .map(bid -> updateBid(bid, bidRequest, errors)) | ||
| .filter(Objects::nonNull) | ||
| .map(bid -> BidderBid.of(bid, BidderUtil.getBidType(bid, impMap), bidResponse.getCur())) | ||
| .toList()) | ||
| .flatMap(Collection::stream) | ||
| .toList(); | ||
| } | ||
|
|
||
| private Bid updateBid(Bid bid, BidRequest bidRequest, List<BidderError> errors) { | ||
| final ObjectNode updateBidExt; | ||
| try { | ||
| updateBidExt = prepareBidExt(bid, bidRequest); | ||
| } catch (PreBidException e) { | ||
| errors.add(BidderError.badServerResponse(e.getMessage())); | ||
| return bid; | ||
| } | ||
|
|
||
| return bid.toBuilder() | ||
| .ext(updateBidExt) | ||
| .build(); | ||
| } | ||
|
|
||
| private ObjectNode prepareBidExt(Bid bid, BidRequest bidRequest) { | ||
| final ObjectNode bidExt = bid.getExt(); | ||
|
|
||
| final String nativoRendererVersion = getNativoRendererVersion(bidRequest); | ||
| if (nativoRendererVersion != null) { | ||
|
|
||
| final ExtPrebid<ExtBidPrebid, ObjectNode> extPrebid = getExtPrebid(bidExt, bid.getId()); | ||
| final ExtBidPrebid extBidPrebid = extPrebid != null ? extPrebid.getPrebid() : null; | ||
|
|
||
| final ExtBidPrebidMeta meta = Optional.ofNullable(extBidPrebid) | ||
| .map(ExtBidPrebid::getMeta) | ||
| .orElse(null); | ||
|
|
||
| final ExtBidPrebidMeta updatedMeta = Optional.ofNullable(meta) | ||
| .map(ExtBidPrebidMeta::toBuilder) | ||
| .orElseGet(ExtBidPrebidMeta::builder) | ||
| .rendererVersion(nativoRendererVersion) | ||
| .build(); | ||
|
|
||
| final ExtBidPrebid modifiedExtBidPrebid = extBidPrebid != null | ||
| ? extBidPrebid.toBuilder().meta(updatedMeta).build() | ||
| : ExtBidPrebid.builder().meta(updatedMeta).build(); | ||
|
|
||
| final ObjectNode updatedBidExt = Optional.ofNullable(bidExt).orElseGet(mapper.mapper()::createObjectNode); | ||
| updatedBidExt.set(PREBID_EXT, mapper.mapper().valueToTree(modifiedExtBidPrebid)); | ||
| return updatedBidExt; | ||
| } | ||
|
|
||
| return bidExt; | ||
| } | ||
|
|
||
| private String getNativoRendererVersion(BidRequest bidRequest) { | ||
| return Optional.ofNullable(bidRequest.getExt()) | ||
| .map(ExtRequest::getPrebid) | ||
| .map(ExtRequestPrebid::getSdk) | ||
| .map(ExtRequestPrebidSdk::getRenderers) | ||
| .orElse(Collections.emptyList()) | ||
| .stream() | ||
| .filter(renderer -> NATIVO_RENDERER_NAME.equals(renderer.getName())) | ||
| .map(ExtRequestPrebidSdkRenderer::getVersion) | ||
| .findFirst() | ||
| .orElse(null); | ||
| } | ||
|
|
||
| private ExtPrebid<ExtBidPrebid, ObjectNode> getExtPrebid(ObjectNode bidExt, String bidId) { | ||
| try { | ||
| return bidExt != null ? mapper.mapper().convertValue(bidExt, EXT_PREBID_TYPE_REFERENCE) : null; | ||
| } catch (IllegalArgumentException e) { | ||
| throw new PreBidException("Invalid ext passed in bid with id: " + bidId); | ||
| } |
Collaborator
There was a problem hiding this comment.
private List<BidderBid> bidsFromResponse(BidRequest bidRequest, BidResponse bidResponse, List<BidderError> errors) {
final Map<String, Imp> impMap = bidRequest.getImp().stream()
.collect(Collectors.toMap(Imp::getId, Function.identity()));
final String rendererVersion = getNativoRendererVersion(bidRequest);
return bidResponse.getSeatbid().stream()
.filter(Objects::nonNull)
.map(SeatBid::getBid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.map(bid -> updateBid(bid, rendererVersion, errors))
.map(bid -> BidderBid.of(bid, BidderUtil.getBidType(bid, impMap), bidResponse.getCur()))
.toList();
}
private static String getNativoRendererVersion(BidRequest bidRequest) {
return Optional.ofNullable(bidRequest.getExt())
.map(ExtRequest::getPrebid)
.map(ExtRequestPrebid::getSdk)
.map(ExtRequestPrebidSdk::getRenderers)
.orElse(Collections.emptyList())
.stream()
.filter(renderer -> NATIVO_RENDERER_NAME.equals(renderer.getName()))
.map(ExtRequestPrebidSdkRenderer::getVersion)
.findFirst()
.orElse(null);
}
private Bid updateBid(Bid bid, String rendererVersion, List<BidderError> errors) {
if (rendererVersion == null) {
return bid;
}
final ObjectNode updateBidExt;
try {
updateBidExt = updateBidExt(bid, rendererVersion);
} catch (PreBidException e) {
errors.add(BidderError.badServerResponse(e.getMessage()));
return bid;
}
return bid.toBuilder().ext(updateBidExt).build();
}
private ObjectNode updateBidExt(Bid bid, String rendererVersion) {
final ObjectNode bidExt = bid.getExt();
final Optional<ExtBidPrebid> extBidPrebid = Optional.ofNullable(bidExt)
.map(ext -> parseExtBidPrebid(bidExt, bid.getId()));
final ExtBidPrebidMeta updatedMeta = extBidPrebid
.map(ExtBidPrebid::getMeta)
.map(ExtBidPrebidMeta::toBuilder)
.orElseGet(ExtBidPrebidMeta::builder)
.rendererVersion(rendererVersion)
.build();
final ExtBidPrebid modifiedExtBidPrebid = extBidPrebid
.map(ExtBidPrebid::toBuilder)
.orElseGet(ExtBidPrebid::builder)
.meta(updatedMeta)
.build();
final ObjectNode updatedBidExt = bidExt != null ? bidExt : mapper.mapper().createObjectNode();
updatedBidExt.set(PREBID_EXT, mapper.mapper().valueToTree(modifiedExtBidPrebid));
return updatedBidExt;
}
private ExtBidPrebid parseExtBidPrebid(ObjectNode bidExt, String bidId) {
try {
return mapper.mapper().convertValue(bidExt, EXT_PREBID_TYPE_REFERENCE).getPrebid();
} catch (IllegalArgumentException e) {
throw new PreBidException("Invalid ext passed in bid with id: " + bidId);
}
}
Comment on lines
268
to
285
| @Test | ||
| public void makeHttpRequestsShouldPassThroughRequestWithIntegerPlacementId() { | ||
| // given | ||
| final ObjectNode impExt = mapper.createObjectNode(); | ||
| impExt.putObject("bidder").put("placementId", 12345678); | ||
| final BidRequest bidRequest = BidRequest.builder() | ||
| .imp(singletonList(Imp.builder().id("imp1").banner(Banner.builder().build()).ext(impExt).build())) | ||
| .build(); | ||
|
|
||
| // when | ||
| final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest); | ||
|
|
||
| // then | ||
| assertThat(result.getErrors()).isEmpty(); | ||
| assertThat(result.getValue()).hasSize(1); | ||
| assertThat(result.getValue().get(0).getPayload().getImp().get(0).getExt() | ||
| .path("bidder").path("placementId").intValue()).isEqualTo(12345678); | ||
| } |
Comment on lines
287
to
304
| @Test | ||
| public void makeHttpRequestsShouldPassThroughRequestWithStringPlacementId() { | ||
| // given | ||
| final ObjectNode impExt = mapper.createObjectNode(); | ||
| impExt.putObject("bidder").put("placementId", "string-placement-id"); | ||
| final BidRequest bidRequest = BidRequest.builder() | ||
| .imp(singletonList(Imp.builder().id("imp1").banner(Banner.builder().build()).ext(impExt).build())) | ||
| .build(); | ||
|
|
||
| // when | ||
| final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest); | ||
|
|
||
| // then | ||
| assertThat(result.getErrors()).isEmpty(); | ||
| assertThat(result.getValue()).hasSize(1); | ||
| assertThat(result.getValue().get(0).getPayload().getImp().get(0).getExt() | ||
| .path("bidder").path("placementId").textValue()).isEqualTo("string-placement-id"); | ||
| } |
Comment on lines
306
to
323
| @Test | ||
| public void makeHttpRequestsShouldPassThroughRequestWithAbsentPlacementId() { | ||
| // given | ||
| final ObjectNode impExt = mapper.createObjectNode(); | ||
| impExt.putObject("bidder"); | ||
| final BidRequest bidRequest = BidRequest.builder() | ||
| .imp(singletonList(Imp.builder().id("imp1").banner(Banner.builder().build()).ext(impExt).build())) | ||
| .build(); | ||
|
|
||
| // when | ||
| final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest); | ||
|
|
||
| // then | ||
| assertThat(result.getErrors()).isEmpty(); | ||
| assertThat(result.getValue()).hasSize(1); | ||
| assertThat(result.getValue().get(0).getPayload().getImp().get(0).getExt() | ||
| .path("bidder").has("placementId")).isFalse(); | ||
| } |
Comment on lines
325
to
359
| @Test | ||
| public void extImpNativoShouldDeserializeIntegerPlacementId() throws JsonProcessingException { | ||
| // given | ||
| final String json = "{\"placementId\": 12345678}"; | ||
|
|
||
| // when | ||
| final ExtImpNativo extImpNativo = mapper.readValue(json, ExtImpNativo.class); | ||
|
|
||
| // then | ||
| assertThat(extImpNativo.getPlacementId()).isEqualTo(12345678); | ||
| } | ||
|
|
||
| @Test | ||
| public void extImpNativoShouldDeserializeStringPlacementId() throws JsonProcessingException { | ||
| // given | ||
| final String json = "{\"placementId\": \"string-placement-id\"}"; | ||
|
|
||
| // when | ||
| final ExtImpNativo extImpNativo = mapper.readValue(json, ExtImpNativo.class); | ||
|
|
||
| // then | ||
| assertThat(extImpNativo.getPlacementId()).isEqualTo("string-placement-id"); | ||
| } | ||
|
|
||
| @Test | ||
| public void extImpNativoShouldDeserializeAbsentPlacementIdAsNull() throws JsonProcessingException { | ||
| // given | ||
| final String json = "{}"; | ||
|
|
||
| // when | ||
| final ExtImpNativo extImpNativo = mapper.readValue(json, ExtImpNativo.class); | ||
|
|
||
| // then | ||
| assertThat(extImpNativo.getPlacementId()).isNull(); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request adds native support for the Nativo bidder to the codebase, moving it from a generic alias-based configuration to a dedicated, fully integrated bidder module. The changes include the implementation of the Nativo bidder logic, configuration, schema, and integration tests, as well as the removal of the old alias-based approach.
Nativo Bidder Integration:
NativoBidderclass, handling bid requests and responses, bid extension updates, and error handling. (NativoBidder.java)ExtImpNativo. (ExtImpNativo.java)NativoConfiguration.java)nativo.yaml)nativo.json)Testing and Documentation:
NativoTest.java,test-auction-nativo-request.json,test-auction-nativo-response.json,test-nativo-bid-request.json,test-nativo-bid-response.json) [1] [2] [3] [4] [5]Configuration and Cleanup:
generic.yaml,test-application.properties,PbsConfig.groovy,BidderParamsSpec.groovy) [1] [2] [3] [4] [5]These changes ensure that Nativo is now a first-class, configurable bidder in the system, with dedicated code, configuration, validation, and tests.
Schema validation:
nativo.jsonto define and validate the parameters accepted by the Nativo adapter.🔧 Type of changes
✨ What's the context?
Adding optional property "placementId" to Nativo Adapter
Adding SDK Renderer Version in Response
🧠 Rationale behind the change
We have already been using this property but it wasn't correctly documented in the adapter.
Our SDK needs the SDK Renderer version to correctly work.
🔎 New Bid Adapter Checklist
🧪 Test plan
How do you know the changes are safe to ship to production?
Added all the necessary tests.
🏎 Quality check