diff --git a/adapters/hypelab/hypelab.go b/adapters/hypelab/hypelab.go new file mode 100644 index 00000000000..c32c0d73fa9 --- /dev/null +++ b/adapters/hypelab/hypelab.go @@ -0,0 +1,303 @@ +package hypelab + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v4/adapters" + "github.com/prebid/prebid-server/v4/config" + "github.com/prebid/prebid-server/v4/errortypes" + "github.com/prebid/prebid-server/v4/openrtb_ext" + "github.com/prebid/prebid-server/v4/util/jsonutil" + "github.com/prebid/prebid-server/v4/version" +) + +const ( + displayManager = "HypeLab Prebid Server" + source = "prebid-server" +) + +type adapter struct { + endpoint string +} + +type bidExt struct { + HypeLab *hypeLabBidExt `json:"hypelab,omitempty"` +} + +type hypeLabBidExt struct { + CreativeType string `json:"creative_type,omitempty"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + return &adapter{endpoint: config.Endpoint}, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + outgoingRequest, errs := makeOutgoingRequest(request) + if len(outgoingRequest.Imp) == 0 { + return nil, errs + } + + body, err := jsonutil.Marshal(outgoingRequest) + if err != nil { + return nil, append(errs, err) + } + + headers := http.Header{} + headers.Add("Accept", "application/json") + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("X-OpenRTB-Version", "2.6") + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Uri: a.endpoint, + Body: body, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(outgoingRequest.Imp), + }}, errs +} + +func makeOutgoingRequest(request *openrtb2.BidRequest) (openrtb2.BidRequest, []error) { + requestCopy := *request + requestCopy.Imp = make([]openrtb2.Imp, 0, len(request.Imp)) + + var errs []error + for _, imp := range request.Imp { + updatedImp, err := makeOutgoingImp(imp) + if err != nil { + errs = append(errs, err) + continue + } + requestCopy.Imp = append(requestCopy.Imp, updatedImp) + } + + if err := setRequestExt(&requestCopy); err != nil { + errs = append(errs, err) + } + + return requestCopy, errs +} + +func makeOutgoingImp(imp openrtb2.Imp) (openrtb2.Imp, error) { + params, err := getImpParams(imp) + if err != nil { + return imp, err + } + + imp.TagID = params.PlacementSlug + imp.DisplayManager = displayManager + imp.DisplayManagerVer = prebidServerVersion() + + imp.Ext, err = jsonutil.Marshal(map[string]openrtb_ext.ExtImpHypeLab{ + "bidder": params, + }) + if err != nil { + return imp, err + } + + return imp, nil +} + +func getImpParams(imp openrtb2.Imp) (openrtb_ext.ExtImpHypeLab, error) { + var ext adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &ext); err != nil { + return openrtb_ext.ExtImpHypeLab{}, &errortypes.BadInput{ + Message: fmt.Sprintf("imp %s: unable to unmarshal ext", imp.ID), + } + } + + var params openrtb_ext.ExtImpHypeLab + if err := jsonutil.Unmarshal(ext.Bidder, ¶ms); err != nil { + return openrtb_ext.ExtImpHypeLab{}, &errortypes.BadInput{ + Message: fmt.Sprintf("imp %s: unable to unmarshal ext.bidder", imp.ID), + } + } + + if params.PropertySlug == "" || params.PlacementSlug == "" { + return openrtb_ext.ExtImpHypeLab{}, &errortypes.BadInput{ + Message: fmt.Sprintf("imp %s: property_slug and placement_slug are required", imp.ID), + } + } + + return params, nil +} + +func setRequestExt(request *openrtb2.BidRequest) error { + var ext map[string]json.RawMessage + if len(request.Ext) > 0 { + if err := jsonutil.Unmarshal(request.Ext, &ext); err != nil { + return err + } + } + if ext == nil { + ext = map[string]json.RawMessage{} + } + + sourceJSON, err := jsonutil.Marshal(source) + if err != nil { + return err + } + providerVersionJSON, err := jsonutil.Marshal("prebid-server@" + prebidServerVersion()) + if err != nil { + return err + } + + ext["source"] = sourceJSON + ext["provider_version"] = providerVersionJSON + + request.Ext, err = jsonutil.Marshal(ext) + return err +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + impLookup := makeImpLookup(request.Imp) + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + + var errs []error + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidType, err := getBidMediaType(&seatBid.Bid[i], impLookup) + if err != nil { + errs = append(errs, err) + continue + } + + typedBid := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + if seatBid.Seat != "" { + typedBid.Seat = openrtb_ext.BidderName(seatBid.Seat) + } + bidResponse.Bids = append(bidResponse.Bids, typedBid) + } + } + + return bidResponse, errs +} + +func makeImpLookup(imps []openrtb2.Imp) map[string]openrtb2.Imp { + lookup := make(map[string]openrtb2.Imp, len(imps)) + for _, imp := range imps { + lookup[imp.ID] = imp + } + return lookup +} + +func getBidMediaType(bid *openrtb2.Bid, impLookup map[string]openrtb2.Imp) (openrtb_ext.BidType, error) { + imp, ok := impLookup[bid.ImpID] + if !ok { + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("bid %s references unknown imp %s", bid.ID, bid.ImpID), + } + } + + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + } + if bid.MType != 0 { + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("bid %s uses unsupported mtype %d", bid.ID, bid.MType), + } + } + + bidType, found, err := getBidMediaTypeFromExt(bid) + if err != nil { + return "", err + } + if found { + return bidType, nil + } + + if strings.HasPrefix(strings.TrimSpace(bid.AdM), ""}, + expected: openrtb_ext.BidTypeVideo, + }, + { + name: "single media type fallback", + bid: openrtb2.Bid{ID: "bid", ImpID: "banner"}, + expected: openrtb_ext.BidTypeBanner, + }, + { + name: "ext without hypelab falls back to imp", + bid: openrtb2.Bid{ID: "bid", ImpID: "banner", Ext: json.RawMessage(`{"other":{}}`)}, + expected: openrtb_ext.BidTypeBanner, + }, + { + name: "ambiguous multiformat fallback", + bid: openrtb2.Bid{ID: "bid", ImpID: "multi"}, + expectedError: "unable to determine media type for bid bid on imp multi", + }, + { + name: "unknown imp", + bid: openrtb2.Bid{ID: "bid", ImpID: "unknown", MType: openrtb2.MarkupBanner}, + expectedError: "bid bid references unknown imp unknown", + }, + { + name: "no media type fallback", + bid: openrtb2.Bid{ID: "bid", ImpID: "empty"}, + expectedError: "unable to determine media type for bid bid on imp empty", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + result, err := getBidMediaType(&test.bid, impLookup) + + if test.expectedError != "" { + require.Error(t, err) + assert.EqualError(t, err, test.expectedError) + return + } + + require.NoError(t, err) + assert.Equal(t, test.expected, result) + }) + } +} + +func TestPrebidServerVersion(t *testing.T) { + originalVersion := version.Ver + t.Cleanup(func() { + version.Ver = originalVersion + }) + + version.Ver = "test-version" + assert.Equal(t, "test-version", prebidServerVersion()) + + version.Ver = "" + assert.Equal(t, version.VerUnknown, prebidServerVersion()) +} diff --git a/adapters/hypelab/hypelabtest/exemplary/fullscreen-video.json b/adapters/hypelab/hypelabtest/exemplary/fullscreen-video.json new file mode 100644 index 00000000000..35aa524bace --- /dev/null +++ b/adapters/hypelab/hypelabtest/exemplary/fullscreen-video.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "test-video-request-id", + "imp": [ + { + "id": "video-imp-id", + "instl": 1, + "video": { + "mimes": ["video/mp4"], + "w": 1080, + "h": 1920 + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "interstitial-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/video" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://api.hypelab.com/v1/rtb_requests", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.6"] + }, + "body": { + "id": "test-video-request-id", + "imp": [ + { + "id": "video-imp-id", + "tagid": "interstitial-placement", + "displaymanager": "HypeLab Prebid Server", + "displaymanagerver": "unknown", + "instl": 1, + "video": { + "mimes": ["video/mp4"], + "w": 1080, + "h": 1920 + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "interstitial-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/video" + }, + "ext": { + "source": "prebid-server", + "provider_version": "prebid-server@unknown" + } + }, + "impIDs": ["video-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-video-request-id", + "seatbid": [ + { + "seat": "hypelab", + "bid": [ + { + "id": "video-bid-id", + "impid": "video-imp-id", + "price": 1.25, + "adm": "", + "adomain": ["advertiser.example"], + "mtype": 2, + "ext": { + "hypelab": { + "creative_type": "video", + "native_render": false + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "seat": "hypelab", + "type": "video", + "bid": { + "id": "video-bid-id", + "impid": "video-imp-id", + "price": 1.25, + "adm": "", + "adomain": ["advertiser.example"], + "mtype": 2, + "ext": { + "hypelab": { + "creative_type": "video", + "native_render": false + } + } + } + } + ] + } + ] +} diff --git a/adapters/hypelab/hypelabtest/exemplary/native.json b/adapters/hypelab/hypelabtest/exemplary/native.json new file mode 100644 index 00000000000..9469c48c92d --- /dev/null +++ b/adapters/hypelab/hypelabtest/exemplary/native.json @@ -0,0 +1,103 @@ +{ + "mockBidRequest": { + "id": "test-native-request-id", + "imp": [ + { + "id": "native-imp-id", + "native": { + "request": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"title\":{\"len\":90}}]}}" + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "native-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/native" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://api.hypelab.com/v1/rtb_requests", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.6"] + }, + "body": { + "id": "test-native-request-id", + "imp": [ + { + "id": "native-imp-id", + "tagid": "native-placement", + "displaymanager": "HypeLab Prebid Server", + "displaymanagerver": "unknown", + "native": { + "request": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"title\":{\"len\":90}}]}}" + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "native-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/native" + }, + "ext": { + "source": "prebid-server", + "provider_version": "prebid-server@unknown" + } + }, + "impIDs": ["native-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-native-request-id", + "seatbid": [ + { + "seat": "hypelab", + "bid": [ + { + "id": "native-bid-id", + "impid": "native-imp-id", + "price": 0.75, + "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"title\":{\"text\":\"Test Native Ad\"}}]}}", + "adomain": ["advertiser.example"], + "mtype": 4 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "seat": "hypelab", + "type": "native", + "bid": { + "id": "native-bid-id", + "impid": "native-imp-id", + "price": 0.75, + "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{\"id\":1,\"title\":{\"text\":\"Test Native Ad\"}}]}}", + "adomain": ["advertiser.example"], + "mtype": 4 + } + } + ] + } + ] +} diff --git a/adapters/hypelab/hypelabtest/exemplary/simple-banner.json b/adapters/hypelab/hypelabtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..daceae08845 --- /dev/null +++ b/adapters/hypelab/hypelabtest/exemplary/simple-banner.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "test-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/article", + "ref": "https://referrer.example/" + }, + "device": { + "ua": "Mozilla/5.0", + "ip": "192.0.2.1", + "language": "en" + }, + "user": { + "buyeruid": "test-user-id" + }, + "regs": { + "gdpr": 0 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://api.hypelab.com/v1/rtb_requests", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.6"] + }, + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-placement", + "displaymanager": "HypeLab Prebid Server", + "displaymanagerver": "unknown", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "test-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/article", + "ref": "https://referrer.example/" + }, + "device": { + "ua": "Mozilla/5.0", + "ip": "192.0.2.1", + "language": "en" + }, + "user": { + "buyeruid": "test-user-id" + }, + "regs": { + "gdpr": 0 + }, + "ext": { + "source": "prebid-server", + "provider_version": "prebid-server@unknown" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "hypelab", + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 0.5, + "adm": "ad", + "adomain": ["advertiser.example"], + "cid": "test-campaign", + "crid": "test-creative", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "curl": "https://api.hypelab.com/click" + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "seat": "hypelab", + "type": "banner", + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 0.5, + "adm": "ad", + "adomain": ["advertiser.example"], + "cid": "test-campaign", + "crid": "test-creative", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "curl": "https://api.hypelab.com/click" + } + } + } + ] + } + ] +} diff --git a/adapters/hypelab/hypelabtest/supplemental/204-response.json b/adapters/hypelab/hypelabtest/supplemental/204-response.json new file mode 100644 index 00000000000..b888642f3f9 --- /dev/null +++ b/adapters/hypelab/hypelabtest/supplemental/204-response.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "test-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/article" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://api.hypelab.com/v1/rtb_requests", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.6"] + }, + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-placement", + "displaymanager": "HypeLab Prebid Server", + "displaymanagerver": "unknown", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "test-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/article" + }, + "ext": { + "source": "prebid-server", + "provider_version": "prebid-server@unknown" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 204 + } + } + ] +} diff --git a/adapters/hypelab/hypelabtest/supplemental/500-response.json b/adapters/hypelab/hypelabtest/supplemental/500-response.json new file mode 100644 index 00000000000..b8f05c35b9c --- /dev/null +++ b/adapters/hypelab/hypelabtest/supplemental/500-response.json @@ -0,0 +1,70 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "test-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/article" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://api.hypelab.com/v1/rtb_requests", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.6"] + }, + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-placement", + "displaymanager": "HypeLab Prebid Server", + "displaymanagerver": "unknown", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "test-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/article" + }, + "ext": { + "source": "prebid-server", + "provider_version": "prebid-server@unknown" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info" + } + ] +} diff --git a/adapters/hypelab/hypelabtest/supplemental/fullscreen-display-without-mtype.json b/adapters/hypelab/hypelabtest/supplemental/fullscreen-display-without-mtype.json new file mode 100644 index 00000000000..3875911e9cb --- /dev/null +++ b/adapters/hypelab/hypelabtest/supplemental/fullscreen-display-without-mtype.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "test-display-request-id", + "imp": [ + { + "id": "display-imp-id", + "instl": 1, + "banner": { + "format": [{ "w": 1080, "h": 1920 }] + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "interstitial-display-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/display" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://api.hypelab.com/v1/rtb_requests", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.6"] + }, + "body": { + "id": "test-display-request-id", + "imp": [ + { + "id": "display-imp-id", + "tagid": "interstitial-display-placement", + "displaymanager": "HypeLab Prebid Server", + "displaymanagerver": "unknown", + "instl": 1, + "banner": { + "format": [{ "w": 1080, "h": 1920 }] + }, + "ext": { + "bidder": { + "property_slug": "test-property", + "placement_slug": "interstitial-display-placement" + } + } + } + ], + "site": { + "page": "https://publisher.example/display" + }, + "ext": { + "source": "prebid-server", + "provider_version": "prebid-server@unknown" + } + }, + "impIDs": ["display-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-display-request-id", + "seatbid": [ + { + "seat": "hypelab", + "bid": [ + { + "id": "display-bid-id", + "impid": "display-imp-id", + "price": 1.10, + "adomain": ["advertiser.example"], + "w": 1080, + "h": 1920, + "ext": { + "hypelab": { + "creative_type": "display", + "native_render": true, + "image_url": "https://cdn.example/display.png" + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "seat": "hypelab", + "type": "banner", + "bid": { + "id": "display-bid-id", + "impid": "display-imp-id", + "price": 1.10, + "adomain": ["advertiser.example"], + "w": 1080, + "h": 1920, + "ext": { + "hypelab": { + "creative_type": "display", + "native_render": true, + "image_url": "https://cdn.example/display.png" + } + } + } + } + ] + } + ] +} diff --git a/adapters/hypelab/params_test.go b/adapters/hypelab/params_test.go new file mode 100644 index 00000000000..2783caf569c --- /dev/null +++ b/adapters/hypelab/params_test.go @@ -0,0 +1,53 @@ +package hypelab + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v4/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderHypeLab, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderHypeLab, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"property_slug":"test-property","placement_slug":"test-placement"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `[]`, + `{}`, + `{"property_slug":"","placement_slug":"test-placement"}`, + `{"property_slug":"test-property","placement_slug":""}`, + `{"property_slug":"test-property"}`, + `{"placement_slug":"test-placement"}`, + `{"property_slug":123,"placement_slug":"test-placement"}`, + `{"property_slug":"test-property","placement_slug":123}`, +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 630fbe348b9..ee7a8d514fb 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -118,6 +118,7 @@ import ( "github.com/prebid/prebid-server/v4/adapters/grid" "github.com/prebid/prebid-server/v4/adapters/gumgum" "github.com/prebid/prebid-server/v4/adapters/huaweiads" + "github.com/prebid/prebid-server/v4/adapters/hypelab" "github.com/prebid/prebid-server/v4/adapters/imds" "github.com/prebid/prebid-server/v4/adapters/impactify" "github.com/prebid/prebid-server/v4/adapters/improvedigital" @@ -386,6 +387,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderGrid: grid.Builder, openrtb_ext.BidderGumGum: gumgum.Builder, openrtb_ext.BidderHuaweiAds: huaweiads.Builder, + openrtb_ext.BidderHypeLab: hypelab.Builder, openrtb_ext.BidderImds: imds.Builder, openrtb_ext.BidderImpactify: impactify.Builder, openrtb_ext.BidderImprovedigital: improvedigital.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 1b61ff724e2..4c9500c2f06 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -135,6 +135,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderGrid, BidderGumGum, BidderHuaweiAds, + BidderHypeLab, BidderImds, BidderImpactify, BidderImprovedigital, @@ -509,6 +510,7 @@ const ( BidderGrid BidderName = "grid" BidderGumGum BidderName = "gumgum" BidderHuaweiAds BidderName = "huaweiads" + BidderHypeLab BidderName = "hypelab" BidderImds BidderName = "imds" BidderImpactify BidderName = "impactify" BidderImprovedigital BidderName = "improvedigital" diff --git a/openrtb_ext/imp_hypelab.go b/openrtb_ext/imp_hypelab.go new file mode 100644 index 00000000000..614f7302c68 --- /dev/null +++ b/openrtb_ext/imp_hypelab.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpHypeLab struct { + PropertySlug string `json:"property_slug"` + PlacementSlug string `json:"placement_slug"` +} diff --git a/static/bidder-info/hype.yaml b/static/bidder-info/hype.yaml new file mode 100644 index 00000000000..f3879d07f6a --- /dev/null +++ b/static/bidder-info/hype.yaml @@ -0,0 +1 @@ +aliasOf: hypelab diff --git a/static/bidder-info/hypelab.yaml b/static/bidder-info/hypelab.yaml new file mode 100644 index 00000000000..c76d6053faf --- /dev/null +++ b/static/bidder-info/hypelab.yaml @@ -0,0 +1,15 @@ +endpoint: "https://api.hypelab.com/v1/rtb_requests" +maintainer: + email: "sdk@hypelab.com" +openrtb: + version: 2.6 +capabilities: + site: + mediaTypes: + - banner + - native + - video +userSync: + redirect: + url: "https://api.hypelab.com/v1/i?redirect_url={{.RedirectURL}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}" + userMacro: "$UID" diff --git a/static/bidder-params/hypelab.json b/static/bidder-params/hypelab.json new file mode 100644 index 00000000000..fd90f96adfc --- /dev/null +++ b/static/bidder-params/hypelab.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "HypeLab Adapter Params", + "description": "A schema which validates params accepted by the HypeLab adapter", + "type": "object", + "properties": { + "property_slug": { + "type": "string", + "minLength": 1, + "description": "HypeLab property slug" + }, + "placement_slug": { + "type": "string", + "minLength": 1, + "description": "HypeLab placement slug" + } + }, + "required": ["property_slug", "placement_slug"] +}