github.com/prebid/prebid-server/v2@v2.18.0/adapters/conversant/conversant.go (about) 1 package conversant 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strings" 8 9 "github.com/prebid/openrtb/v20/adcom1" 10 "github.com/prebid/openrtb/v20/openrtb2" 11 "github.com/prebid/prebid-server/v2/adapters" 12 "github.com/prebid/prebid-server/v2/config" 13 "github.com/prebid/prebid-server/v2/errortypes" 14 "github.com/prebid/prebid-server/v2/openrtb_ext" 15 ) 16 17 type ConversantAdapter struct { 18 URI string 19 } 20 21 func (c *ConversantAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 22 //Backend needs USD or it will reject the request 23 if len(request.Cur) > 0 && request.Cur[0] != "USD" { 24 request.Cur = []string{"USD"} 25 } 26 for i := 0; i < len(request.Imp); i++ { 27 var bidderExt adapters.ExtImpBidder 28 if err := json.Unmarshal(request.Imp[i].Ext, &bidderExt); err != nil { 29 return nil, []error{&errortypes.BadInput{ 30 Message: fmt.Sprintf("Impression[%d] missing ext object", i), 31 }} 32 } 33 34 var cnvrExt openrtb_ext.ExtImpConversant 35 if err := json.Unmarshal(bidderExt.Bidder, &cnvrExt); err != nil { 36 return nil, []error{&errortypes.BadInput{ 37 Message: fmt.Sprintf("Impression[%d] missing ext.bidder object", i), 38 }} 39 } 40 41 if cnvrExt.SiteID == "" { 42 return nil, []error{&errortypes.BadInput{ 43 Message: fmt.Sprintf("Impression[%d] requires ext.bidder.site_id", i), 44 }} 45 } 46 47 if i == 0 { 48 if request.Site != nil { 49 tmpSite := *request.Site 50 request.Site = &tmpSite 51 request.Site.ID = cnvrExt.SiteID 52 } else if request.App != nil { 53 tmpApp := *request.App 54 request.App = &tmpApp 55 request.App.ID = cnvrExt.SiteID 56 } 57 } 58 err := parseCnvrParams(&request.Imp[i], cnvrExt, reqInfo) 59 if err != nil { 60 return nil, err 61 } 62 } 63 64 //create the request body 65 data, err := json.Marshal(request) 66 if err != nil { 67 return nil, []error{&errortypes.BadInput{ 68 Message: "Error in packaging request to JSON", 69 }} 70 } 71 headers := http.Header{} 72 headers.Add("Content-Type", "application/json;charset=utf-8") 73 headers.Add("Accept", "application/json") 74 75 return []*adapters.RequestData{{ 76 Method: "POST", 77 Uri: c.URI, 78 Body: data, 79 Headers: headers, 80 ImpIDs: openrtb_ext.GetImpIDs(request.Imp), 81 }}, nil 82 } 83 84 func parseCnvrParams(imp *openrtb2.Imp, cnvrExt openrtb_ext.ExtImpConversant, reqInfo *adapters.ExtraRequestInfo) []error { 85 imp.DisplayManager = "prebid-s2s" 86 imp.DisplayManagerVer = "2.0.0" 87 88 if imp.BidFloor <= 0 && cnvrExt.BidFloor > 0 { 89 imp.BidFloor = cnvrExt.BidFloor 90 } 91 92 if len(cnvrExt.TagID) > 0 { 93 imp.TagID = cnvrExt.TagID 94 } 95 96 // Take care not to override the global secure flag 97 if (imp.Secure == nil || *imp.Secure == 0) && cnvrExt.Secure != nil { 98 imp.Secure = cnvrExt.Secure 99 } 100 101 var position *adcom1.PlacementPosition 102 if cnvrExt.Position != nil { 103 position = adcom1.PlacementPosition(*cnvrExt.Position).Ptr() 104 } 105 if imp.Banner != nil { 106 tmpBanner := *imp.Banner 107 imp.Banner = &tmpBanner 108 imp.Banner.Pos = position 109 110 } else if imp.Video != nil { 111 tmpVideo := *imp.Video 112 imp.Video = &tmpVideo 113 imp.Video.Pos = position 114 115 if len(cnvrExt.API) > 0 { 116 imp.Video.API = make([]adcom1.APIFramework, 0, len(cnvrExt.API)) 117 for _, api := range cnvrExt.API { 118 imp.Video.API = append(imp.Video.API, adcom1.APIFramework(api)) 119 } 120 } 121 122 // Include protocols, mimes, and max duration if specified 123 // These properties can also be specified in ad unit's video object, 124 // but are overridden if the custom params object also contains them. 125 126 if len(cnvrExt.Protocols) > 0 { 127 imp.Video.Protocols = make([]adcom1.MediaCreativeSubtype, 0, len(cnvrExt.Protocols)) 128 for _, protocol := range cnvrExt.Protocols { 129 imp.Video.Protocols = append(imp.Video.Protocols, adcom1.MediaCreativeSubtype(protocol)) 130 } 131 } 132 133 if len(cnvrExt.MIMEs) > 0 { 134 imp.Video.MIMEs = make([]string, len(cnvrExt.MIMEs)) 135 copy(imp.Video.MIMEs, cnvrExt.MIMEs) 136 } 137 138 if cnvrExt.MaxDuration != nil { 139 imp.Video.MaxDuration = *cnvrExt.MaxDuration 140 } 141 } 142 if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != "USD" { 143 floor, err := reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD") 144 if err != nil { 145 return []error{&errortypes.BadInput{ 146 Message: fmt.Sprintf("Unable to convert provided bid floor currency from %s to USD", imp.BidFloorCur), 147 }} 148 } 149 imp.BidFloorCur = "USD" 150 imp.BidFloor = floor 151 } 152 return nil 153 } 154 155 func (c *ConversantAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 156 if response.StatusCode == http.StatusNoContent { 157 return nil, nil // no bid response 158 } 159 160 if response.StatusCode != http.StatusOK { 161 return nil, []error{&errortypes.BadServerResponse{ 162 Message: fmt.Sprintf("Unexpected status code: %d", response.StatusCode), 163 }} 164 } 165 166 var resp openrtb2.BidResponse 167 if err := json.Unmarshal(response.Body, &resp); err != nil { 168 return nil, []error{&errortypes.BadServerResponse{ 169 Message: fmt.Sprintf("bad server response: %d. ", err), 170 }} 171 } 172 173 if len(resp.SeatBid) == 0 { 174 return nil, []error{&errortypes.BadServerResponse{ 175 Message: "Empty bid request", 176 }} 177 } 178 179 bids := resp.SeatBid[0].Bid 180 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bids)) 181 for i := 0; i < len(bids); i++ { 182 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 183 Bid: &bids[i], 184 BidType: getBidType(bids[i].ImpID, internalRequest.Imp), 185 }) 186 } 187 return bidResponse, nil 188 } 189 190 func getBidType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { 191 bidType := openrtb_ext.BidTypeBanner 192 for _, imp := range imps { 193 if imp.ID == impId { 194 if imp.Video != nil { 195 bidType = openrtb_ext.BidTypeVideo 196 } 197 break 198 } 199 } 200 return bidType 201 } 202 203 // Builder builds a new instance of the Conversant adapter for the given bidder with the given config. 204 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 205 bidder := &ConversantAdapter{ 206 URI: config.Endpoint, 207 } 208 return bidder, nil 209 }