diff --git a/adapters/aniview/aniview.go b/adapters/aniview/aniview.go
new file mode 100644
index 00000000000..cc2bfdc7c77
--- /dev/null
+++ b/adapters/aniview/aniview.go
@@ -0,0 +1,199 @@
+package aniview
+
+import (
+ "bytes"
+ "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"
+)
+
+type adapter struct {
+ endpoint string
+}
+
+type aniviewExt struct {
+ PBS int `json:"pbs"`
+}
+
+// Builder builds a new instance of the Aniview adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+ bidder := &adapter{
+ endpoint: config.Endpoint,
+ }
+ return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ var requests []*adapters.RequestData
+ var errors []error
+
+ requestExt, err := buildRequestExt(request.Ext)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ // One outgoing request per imp and per media type, mirroring the Prebid.js adapter.
+ for _, imp := range request.Imp {
+ impExt, err := extractImpExt(&imp)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ imp.TagID = strings.TrimSpace(impExt.ChannelId)
+
+ for _, singleTypeImp := range splitImpByMediaType(imp) {
+ requestCopy := *request
+ requestCopy.Imp = []openrtb2.Imp{singleTypeImp}
+ requestCopy.Ext = requestExt
+
+ requestJSON, err := jsonutil.Marshal(&requestCopy)
+ if err != nil {
+ errors = append(errors, fmt.Errorf("marshal bidRequest: %w", err))
+ continue
+ }
+
+ // Headers must be a fresh map per request: RequestData.Headers is
+ // mutable and shared maps would leak mutations across requests.
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ requests = append(requests, &adapters.RequestData{
+ Method: "POST",
+ Uri: a.endpoint,
+ Body: requestJSON,
+ Headers: headers,
+ ImpIDs: []string{singleTypeImp.ID},
+ })
+ }
+ }
+
+ return requests, errors
+}
+
+// buildRequestExt merges ext.aniview into the existing request.ext, preserving
+// whatever PBS core has put there.
+func buildRequestExt(requestExt []byte) ([]byte, error) {
+ extMap := map[string]interface{}{}
+ if len(requestExt) > 0 {
+ if err := jsonutil.Unmarshal(requestExt, &extMap); err != nil {
+ return nil, fmt.Errorf("unmarshal request.ext: %w", err)
+ }
+ }
+
+ extMap["aniview"] = aniviewExt{PBS: 1}
+
+ ext, err := jsonutil.Marshal(extMap)
+ if err != nil {
+ return nil, fmt.Errorf("marshal request.ext: %w", err)
+ }
+ return ext, nil
+}
+
+// splitImpByMediaType returns one imp per media type, so each outgoing request
+// carries a single media type, as the Aniview endpoint expects.
+func splitImpByMediaType(imp openrtb2.Imp) []openrtb2.Imp {
+ if imp.Video == nil || imp.Banner == nil {
+ return []openrtb2.Imp{imp}
+ }
+
+ videoImp := imp
+ videoImp.Banner = nil
+ bannerImp := imp
+ bannerImp.Video = nil
+ return []openrtb2.Imp{videoImp, bannerImp}
+}
+
+func extractImpExt(imp *openrtb2.Imp) (*openrtb_ext.ImpExtAniview, error) {
+ var bidderExt adapters.ExtImpBidder
+ if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return nil, fmt.Errorf("unmarshal bidderExt: %w", err)
+ }
+
+ var impExt openrtb_ext.ImpExtAniview
+ if err := jsonutil.Unmarshal(bidderExt.Bidder, &impExt); err != nil {
+ return nil, fmt.Errorf("unmarshal ImpExtAniview: %w", err)
+ }
+
+ if strings.TrimSpace(impExt.ChannelId) == "" {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("Missing AV_CHANNELID for imp: %s", imp.ID),
+ }
+ }
+ return &impExt, nil
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ var errs []error
+
+ if adapters.IsResponseStatusCodeNoContent(responseData) {
+ return nil, nil
+ }
+
+ if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
+ return nil, []error{err}
+ }
+
+ // The exchange may answer a no-bid as 200 with an empty/whitespace body.
+ if len(bytes.TrimSpace(responseData.Body)) == 0 {
+ return nil, nil
+ }
+
+ var response openrtb2.BidResponse
+ if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("bad server response: %s", err),
+ }}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(response.SeatBid))
+
+ if response.Cur != "" {
+ bidResponse.Currency = response.Cur
+ }
+
+ for _, seatBid := range response.SeatBid {
+ for i, bid := range seatBid.Bid {
+ // Mirror the Prebid.js adapter: a bid without markup or a VAST url is unusable.
+ if bid.AdM == "" && bid.NURL == "" {
+ continue
+ }
+
+ bidType, err := getMediaTypeForBid(&bid)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &seatBid.Bid[i],
+ BidType: bidType,
+ })
+ }
+ }
+
+ return bidResponse, errs
+}
+
+// getMediaTypeForBid resolves the bid media type from bid.mtype, which the
+// exchange sets explicitly on every bid.
+func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) {
+ switch bid.MType {
+ case openrtb2.MarkupBanner:
+ return openrtb_ext.BidTypeBanner, nil
+ case openrtb2.MarkupVideo:
+ return openrtb_ext.BidTypeVideo, nil
+ default:
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Could not define bid type for imp: %s", bid.ImpID),
+ }
+ }
+}
diff --git a/adapters/aniview/aniview_test.go b/adapters/aniview/aniview_test.go
new file mode 100644
index 00000000000..79780211355
--- /dev/null
+++ b/adapters/aniview/aniview_test.go
@@ -0,0 +1,48 @@
+package aniview
+
+import (
+ "testing"
+
+ "github.com/prebid/prebid-server/v4/adapters"
+ "github.com/prebid/prebid-server/v4/adapters/adapterstest"
+ "github.com/prebid/prebid-server/v4/config"
+ "github.com/prebid/prebid-server/v4/openrtb_ext"
+)
+
+func TestBuildRequestExtInvalid(t *testing.T) {
+ if _, err := buildRequestExt([]byte(`"not-an-object"`)); err == nil {
+ t.Error("expected error for non-object request.ext")
+ }
+ ext, err := buildRequestExt([]byte(`{"prebid":{"debug":true}}`))
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if string(ext) == "" {
+ t.Error("expected merged ext")
+ }
+}
+
+func TestMakeBidsEmptyBody(t *testing.T) {
+ bidder, _ := Builder(openrtb_ext.BidderAniview, config.Adapter{Endpoint: "https://rtb.aniview.com/sspRTB2"}, config.Server{})
+ for _, body := range []string{"", "\n", " \n"} {
+ resp, errs := bidder.(*adapter).MakeBids(nil, &adapters.RequestData{Body: []byte("{}")}, &adapters.ResponseData{StatusCode: 200, Body: []byte(body)})
+ if resp != nil || errs != nil {
+ t.Errorf("empty body %q should be a silent no-bid, got resp=%v errs=%v", body, resp, errs)
+ }
+ }
+}
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderAniview, config.Adapter{
+ Endpoint: "https://rtb.aniview.com/sspRTB2",
+ },
+ config.Server{
+ ExternalUrl: "http://hosturl.com", GvlID: 780, DataCenter: "2",
+ })
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "aniviewtest", bidder)
+}
diff --git a/adapters/aniview/aniviewtest/exemplary/banner.json b/adapters/aniview/aniviewtest/exemplary/banner.json
new file mode 100644
index 00000000000..3ca49d136eb
--- /dev/null
+++ b/adapters/aniview/aniviewtest/exemplary/banner.json
@@ -0,0 +1,106 @@
+{
+ "mockBidRequest": {
+ "id": "banner-request-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "banner-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250,
+ "format": [{"w": 300, "h": 250}]
+ },
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://rtb.aniview.com/sspRTB2",
+ "body": {
+ "id": "banner-request-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "banner-imp-id",
+ "tagid": "abcdef1234567890abcdef12",
+ "banner": {
+ "w": 300,
+ "h": 250,
+ "format": [{"w": 300, "h": 250}]
+ },
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ],
+ "ext": {
+ "aniview": {
+ "pbs": 1
+ }
+ }
+ },
+ "impIDs": ["banner-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "banner-request-id",
+ "seatbid": [
+ {
+ "seat": "1234567890abcdef12345678",
+ "bid": [
+ {
+ "id": "banner-bid-id",
+ "impid": "banner-imp-id",
+ "price": 1.2,
+ "adm": "
",
+ "crid": "banner-creative-id",
+ "adomain": ["advertiser.com"],
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "banner-bid-id",
+ "impid": "banner-imp-id",
+ "price": 1.2,
+ "adm": "",
+ "crid": "banner-creative-id",
+ "adomain": ["advertiser.com"],
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ],
+ "expectedMakeBidsErrors": []
+}
diff --git a/adapters/aniview/aniviewtest/exemplary/multi-format.json b/adapters/aniview/aniviewtest/exemplary/multi-format.json
new file mode 100644
index 00000000000..25d83df471e
--- /dev/null
+++ b/adapters/aniview/aniviewtest/exemplary/multi-format.json
@@ -0,0 +1,172 @@
+{
+ "mockBidRequest": {
+ "id": "multi-request-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "multi-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://rtb.aniview.com/sspRTB2",
+ "body": {
+ "id": "multi-request-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "multi-imp-id",
+ "tagid": "abcdef1234567890abcdef12",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ],
+ "ext": {
+ "aniview": {
+ "pbs": 1
+ }
+ }
+ },
+ "impIDs": ["multi-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "multi-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "video-bid-id",
+ "impid": "multi-imp-id",
+ "price": 3.1,
+ "adm": "Aniview",
+ "mtype": 2
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ {
+ "expectedRequest": {
+ "uri": "https://rtb.aniview.com/sspRTB2",
+ "body": {
+ "id": "multi-request-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "multi-imp-id",
+ "tagid": "abcdef1234567890abcdef12",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ],
+ "ext": {
+ "aniview": {
+ "pbs": 1
+ }
+ }
+ },
+ "impIDs": ["multi-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "multi-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "banner-bid-id",
+ "impid": "multi-imp-id",
+ "price": 0.9,
+ "adm": "banner creative
",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "video-bid-id",
+ "impid": "multi-imp-id",
+ "price": 3.1,
+ "adm": "Aniview",
+ "mtype": 2
+ },
+ "type": "video"
+ }
+ ]
+ },
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "banner-bid-id",
+ "impid": "multi-imp-id",
+ "price": 0.9,
+ "adm": "banner creative
",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ],
+ "expectedMakeBidsErrors": []
+}
diff --git a/adapters/aniview/aniviewtest/exemplary/video.json b/adapters/aniview/aniviewtest/exemplary/video.json
new file mode 100644
index 00000000000..43fcf510fdf
--- /dev/null
+++ b/adapters/aniview/aniviewtest/exemplary/video.json
@@ -0,0 +1,121 @@
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "device": {
+ "ua": "Mozilla/5.0",
+ "ip": "1.2.3.4"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480,
+ "protocols": [2, 3, 5, 6]
+ },
+ "bidfloor": 0.5,
+ "bidfloorcur": "USD",
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ],
+ "tmax": 3000
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://rtb.aniview.com/sspRTB2",
+ "body": {
+ "id": "some-request-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "device": {
+ "ua": "Mozilla/5.0",
+ "ip": "1.2.3.4"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "tagid": "abcdef1234567890abcdef12",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480,
+ "protocols": [2, 3, 5, 6]
+ },
+ "bidfloor": 0.5,
+ "bidfloorcur": "USD",
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ],
+ "ext": {
+ "aniview": {
+ "pbs": 1
+ }
+ },
+ "tmax": 3000
+ },
+ "impIDs": ["some-impression-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "some-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "1234567890abcdef12345678",
+ "bid": [
+ {
+ "id": "some-bid-id",
+ "impid": "some-impression-id",
+ "price": 2.5,
+ "adm": "AniviewAd00:00:15",
+ "adid": "some-adsource-id",
+ "crid": "some-creative-id",
+ "adomain": ["advertiser.com"],
+ "mtype": 2
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "some-bid-id",
+ "impid": "some-impression-id",
+ "price": 2.5,
+ "adm": "AniviewAd00:00:15",
+ "adid": "some-adsource-id",
+ "crid": "some-creative-id",
+ "adomain": ["advertiser.com"],
+ "mtype": 2
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ],
+ "expectedMakeBidsErrors": []
+}
diff --git a/adapters/aniview/aniviewtest/supplemental/bad-imp-ext.json b/adapters/aniview/aniviewtest/supplemental/bad-imp-ext.json
new file mode 100644
index 00000000000..f12b52667de
--- /dev/null
+++ b/adapters/aniview/aniviewtest/supplemental/bad-imp-ext.json
@@ -0,0 +1,30 @@
+{
+ "mockBidRequest": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "ext": {
+ "bidder": "not-an-object"
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480
+ }
+ }
+ ]
+ },
+ "httpCalls": [],
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "^unmarshal ImpExtAniview",
+ "comparison": "regex"
+ }
+ ]
+}
diff --git a/adapters/aniview/aniviewtest/supplemental/invalid-response.json b/adapters/aniview/aniviewtest/supplemental/invalid-response.json
new file mode 100644
index 00000000000..4c1e69101f7
--- /dev/null
+++ b/adapters/aniview/aniviewtest/supplemental/invalid-response.json
@@ -0,0 +1,77 @@
+{
+ "mockBidRequest": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://rtb.aniview.com/sspRTB2",
+ "body": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480
+ },
+ "tagid": "abcdef1234567890abcdef12"
+ }
+ ],
+ "ext": {
+ "aniview": {
+ "pbs": 1
+ }
+ }
+ },
+ "impIDs": [
+ "imp-1"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": "not-a-bid-response"
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "^bad server response",
+ "comparison": "regex"
+ }
+ ]
+}
diff --git a/adapters/aniview/aniviewtest/supplemental/missing-channelid.json b/adapters/aniview/aniviewtest/supplemental/missing-channelid.json
new file mode 100644
index 00000000000..31b88f5c823
--- /dev/null
+++ b/adapters/aniview/aniviewtest/supplemental/missing-channelid.json
@@ -0,0 +1,33 @@
+{
+ "mockBidRequest": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": " "
+ }
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480
+ }
+ }
+ ]
+ },
+ "httpCalls": [],
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Missing AV_CHANNELID for imp: imp-1",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/aniview/aniviewtest/supplemental/missing-mtype.json b/adapters/aniview/aniviewtest/supplemental/missing-mtype.json
new file mode 100644
index 00000000000..23eb4509c57
--- /dev/null
+++ b/adapters/aniview/aniviewtest/supplemental/missing-mtype.json
@@ -0,0 +1,123 @@
+{
+ "mockBidRequest": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480
+ }
+ }
+ ],
+ "ext": {
+ "prebid": {
+ "debug": true
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://rtb.aniview.com/sspRTB2",
+ "body": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480
+ },
+ "tagid": "abcdef1234567890abcdef12"
+ }
+ ],
+ "ext": {
+ "prebid": {
+ "debug": true
+ },
+ "aniview": {
+ "pbs": 1
+ }
+ }
+ },
+ "impIDs": [
+ "imp-1"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "req-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "b-mtype",
+ "impid": "imp-1",
+ "price": 2.0,
+ "adm": "",
+ "mtype": 2
+ },
+ {
+ "id": "b-sniff",
+ "impid": "other-imp",
+ "price": 1.5,
+ "adm": " "
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "b-mtype",
+ "impid": "imp-1",
+ "price": 2.0,
+ "adm": "",
+ "mtype": 2
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Could not define bid type for imp: other-imp",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/aniview/aniviewtest/supplemental/no-bid.json b/adapters/aniview/aniviewtest/supplemental/no-bid.json
new file mode 100644
index 00000000000..f5ef9d9f15b
--- /dev/null
+++ b/adapters/aniview/aniviewtest/supplemental/no-bid.json
@@ -0,0 +1,74 @@
+{
+ "mockBidRequest": {
+ "id": "nobid-request-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "nobid-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://rtb.aniview.com/sspRTB2",
+ "body": {
+ "id": "nobid-request-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "nobid-imp-id",
+ "tagid": "abcdef1234567890abcdef12",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ],
+ "ext": {
+ "aniview": {
+ "pbs": 1
+ }
+ }
+ },
+ "impIDs": ["nobid-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "nobid-request-id",
+ "seatbid": []
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": []
+ }
+ ],
+ "expectedMakeBidsErrors": []
+}
diff --git a/adapters/aniview/aniviewtest/supplemental/no-content.json b/adapters/aniview/aniviewtest/supplemental/no-content.json
new file mode 100644
index 00000000000..ce70c188d23
--- /dev/null
+++ b/adapters/aniview/aniviewtest/supplemental/no-content.json
@@ -0,0 +1,72 @@
+{
+ "mockBidRequest": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://rtb.aniview.com/sspRTB2",
+ "body": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ },
+ "tagid": "abcdef1234567890abcdef12"
+ }
+ ],
+ "ext": {
+ "aniview": {
+ "pbs": 1
+ }
+ }
+ },
+ "impIDs": [
+ "imp-1"
+ ]
+ },
+ "mockResponse": {
+ "status": 204,
+ "body": {}
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/adapters/aniview/aniviewtest/supplemental/nurl-video.json b/adapters/aniview/aniviewtest/supplemental/nurl-video.json
new file mode 100644
index 00000000000..698a806d367
--- /dev/null
+++ b/adapters/aniview/aniviewtest/supplemental/nurl-video.json
@@ -0,0 +1,102 @@
+{
+ "mockBidRequest": {
+ "id": "nurl-request-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "nurl-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://rtb.aniview.com/sspRTB2",
+ "body": {
+ "id": "nurl-request-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "nurl-imp-id",
+ "tagid": "abcdef1234567890abcdef12",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ }
+ }
+ ],
+ "ext": {
+ "aniview": {
+ "pbs": 1
+ }
+ }
+ },
+ "impIDs": ["nurl-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "nurl-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "nurl-bid-id",
+ "impid": "nurl-imp-id",
+ "price": 1.7,
+ "nurl": "https://rtb.aniview.com/vast?bid=nurl-bid-id",
+ "mtype": 2
+ },
+ {
+ "id": "empty-bid-id",
+ "impid": "nurl-imp-id",
+ "price": 1.1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "nurl-bid-id",
+ "impid": "nurl-imp-id",
+ "price": 1.7,
+ "nurl": "https://rtb.aniview.com/vast?bid=nurl-bid-id",
+ "mtype": 2
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ],
+ "expectedMakeBidsErrors": []
+}
diff --git a/adapters/aniview/aniviewtest/supplemental/status-400.json b/adapters/aniview/aniviewtest/supplemental/status-400.json
new file mode 100644
index 00000000000..760977daf9f
--- /dev/null
+++ b/adapters/aniview/aniviewtest/supplemental/status-400.json
@@ -0,0 +1,77 @@
+{
+ "mockBidRequest": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://rtb.aniview.com/sspRTB2",
+ "body": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480
+ },
+ "tagid": "abcdef1234567890abcdef12"
+ }
+ ],
+ "ext": {
+ "aniview": {
+ "pbs": 1
+ }
+ }
+ },
+ "impIDs": [
+ "imp-1"
+ ]
+ },
+ "mockResponse": {
+ "status": 400,
+ "body": {}
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/aniview/aniviewtest/supplemental/unknown-bid-type.json b/adapters/aniview/aniviewtest/supplemental/unknown-bid-type.json
new file mode 100644
index 00000000000..78d5e8d94e8
--- /dev/null
+++ b/adapters/aniview/aniviewtest/supplemental/unknown-bid-type.json
@@ -0,0 +1,90 @@
+{
+ "mockBidRequest": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ },
+ "banner": {
+ "w": 300,
+ "h": 250
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://rtb.aniview.com/sspRTB2",
+ "body": {
+ "id": "req-id",
+ "site": {
+ "page": "https://publisher.com/page"
+ },
+ "imp": [
+ {
+ "id": "imp-1",
+ "ext": {
+ "bidder": {
+ "AV_PUBLISHERID": "1234567890abcdef12345678",
+ "AV_CHANNELID": "abcdef1234567890abcdef12"
+ }
+ },
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "tagid": "abcdef1234567890abcdef12"
+ }
+ ],
+ "ext": {
+ "aniview": {
+ "pbs": 1
+ }
+ }
+ },
+ "impIDs": [
+ "imp-1"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "req-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "b1",
+ "impid": "other-imp",
+ "price": 1.0,
+ "adm": "plain text creative"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": []
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Could not define bid type for imp: other-imp",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/aniview/params_test.go b/adapters/aniview/params_test.go
new file mode 100644
index 00000000000..ebbeabb1839
--- /dev/null
+++ b/adapters/aniview/params_test.go
@@ -0,0 +1,48 @@
+package aniview
+
+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 _, validParam := range validParams {
+ if err := validator.Validate(openrtb_ext.BidderAniview, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected valid params: %s", validParam)
+ }
+ }
+}
+
+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.BidderAniview, json.RawMessage(p)); err == nil {
+ t.Errorf("Schema allowed invalid params: %s", p)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"AV_PUBLISHERID": "1234567890abcdef12345678", "AV_CHANNELID": "abcdef1234567890abcdef12"}`,
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `{}`,
+ `{"AV_PUBLISHERID": "1234567890abcdef12345678"}`,
+ `{"AV_CHANNELID": "abcdef1234567890abcdef12"}`,
+ `{"AV_PUBLISHERID": 123, "AV_CHANNELID": "abcdef1234567890abcdef12"}`,
+ `{"AV_PUBLISHERID": "", "AV_CHANNELID": "abcdef1234567890abcdef12"}`,
+}
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 75a848b7596..8f0f63b5bc6 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -40,6 +40,7 @@ import (
"github.com/prebid/prebid-server/v4/adapters/alkimi"
alliance_gravity "github.com/prebid/prebid-server/v4/adapters/alliance_gravity"
"github.com/prebid/prebid-server/v4/adapters/amx"
+ "github.com/prebid/prebid-server/v4/adapters/aniview"
"github.com/prebid/prebid-server/v4/adapters/apacdex"
"github.com/prebid/prebid-server/v4/adapters/appnexus"
"github.com/prebid/prebid-server/v4/adapters/appush"
@@ -312,6 +313,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
openrtb_ext.BidderAlkimi: alkimi.Builder,
openrtb_ext.BidderAllianceGravity: alliance_gravity.Builder,
openrtb_ext.BidderAMX: amx.Builder,
+ openrtb_ext.BidderAniview: aniview.Builder,
openrtb_ext.BidderApacdex: apacdex.Builder,
openrtb_ext.BidderAppnexus: appnexus.Builder,
openrtb_ext.BidderAppush: appush.Builder,
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index aa725e7d1bd..874bb71b37f 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -56,6 +56,7 @@ var coreBidderNames []BidderName = []BidderName{
BidderAlkimi,
BidderAllianceGravity,
BidderAMX,
+ BidderAniview,
BidderApacdex,
BidderAppnexus,
BidderAppush,
@@ -435,6 +436,7 @@ const (
BidderAlkimi BidderName = "alkimi"
BidderAllianceGravity BidderName = "alliance_gravity"
BidderAMX BidderName = "amx"
+ BidderAniview BidderName = "aniview"
BidderApacdex BidderName = "apacdex"
BidderAppnexus BidderName = "appnexus"
BidderAppush BidderName = "appush"
diff --git a/openrtb_ext/imp_aniview.go b/openrtb_ext/imp_aniview.go
new file mode 100644
index 00000000000..7b6fd80e51b
--- /dev/null
+++ b/openrtb_ext/imp_aniview.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+// ImpExtAniview defines the contract for bidrequest.imp[i].ext.prebid.bidder.aniview
+type ImpExtAniview struct {
+ PublisherId string `json:"AV_PUBLISHERID"`
+ ChannelId string `json:"AV_CHANNELID"`
+}
diff --git a/static/bidder-info/aniview.yaml b/static/bidder-info/aniview.yaml
new file mode 100644
index 00000000000..5e16d1ac8b3
--- /dev/null
+++ b/static/bidder-info/aniview.yaml
@@ -0,0 +1,18 @@
+endpoint: "https://rtb.aniview.com/sspRTB2"
+maintainer:
+ email: "support@aniview.com"
+gvlVendorID: 780
+endpointCompression: gzip
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ site:
+ mediaTypes:
+ - banner
+ - video
+userSync:
+ iframe:
+ url: https://player.aniview.com/ssync/ssync.html?pbs=1&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&st=iframe&r={{.RedirectURL}}
+ userMacro: "[AV_UID]"
diff --git a/static/bidder-params/aniview.json b/static/bidder-params/aniview.json
new file mode 100644
index 00000000000..6c0bd48bd66
--- /dev/null
+++ b/static/bidder-params/aniview.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Aniview Adapter Params",
+ "description": "A schema which validates params accepted by the Aniview adapter",
+ "type": "object",
+ "properties": {
+ "AV_PUBLISHERID": {
+ "type": "string",
+ "description": "The Aniview publisher (network) id.",
+ "minLength": 1
+ },
+ "AV_CHANNELID": {
+ "type": "string",
+ "description": "The Aniview channel id.",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "AV_PUBLISHERID",
+ "AV_CHANNELID"
+ ]
+}