github.com/prebid/prebid-server/v2@v2.18.0/adapters/gumgum/gumgum.go (about) 1 package gumgum 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strconv" 8 "strings" 9 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 // adapter implements Bidder interface. 18 type adapter struct { 19 URI string 20 } 21 22 // MakeRequests makes the HTTP requests which should be made to fetch bids. 23 func (g *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 24 var validImps []openrtb2.Imp 25 var siteCopy openrtb2.Site 26 if request.Site != nil { 27 siteCopy = *request.Site 28 } 29 30 numRequests := len(request.Imp) 31 errs := make([]error, 0, numRequests) 32 33 for i := 0; i < numRequests; i++ { 34 imp := request.Imp[i] 35 gumgumExt, err := preprocess(&imp) 36 if err != nil { 37 errs = append(errs, err) 38 } else { 39 if gumgumExt.Zone != "" { 40 siteCopy.ID = gumgumExt.Zone 41 } 42 43 if gumgumExt.PubID != 0 { 44 if siteCopy.Publisher != nil { 45 siteCopy.Publisher.ID = strconv.FormatFloat(gumgumExt.PubID, 'f', -1, 64) 46 } else { 47 siteCopy.Publisher = &openrtb2.Publisher{ID: strconv.FormatFloat(gumgumExt.PubID, 'f', -1, 64)} 48 } 49 } 50 51 validImps = append(validImps, imp) 52 } 53 } 54 55 if len(validImps) == 0 { 56 return nil, errs 57 } 58 59 request.Imp = validImps 60 61 if request.Site != nil { 62 request.Site = &siteCopy 63 } 64 65 reqJSON, err := json.Marshal(request) 66 if err != nil { 67 errs = append(errs, err) 68 return nil, errs 69 } 70 71 headers := http.Header{} 72 headers.Add("Content-Type", "application/json;charset=utf-8") 73 headers.Add("Accept", "application/json") 74 return []*adapters.RequestData{{ 75 Method: "POST", 76 Uri: g.URI, 77 Body: reqJSON, 78 Headers: headers, 79 ImpIDs: openrtb_ext.GetImpIDs(request.Imp), 80 }}, errs 81 } 82 83 // MakeBids unpacks the server's response into Bids. 84 func (g *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 85 if response.StatusCode == http.StatusNoContent { 86 return nil, nil 87 } 88 89 if response.StatusCode == http.StatusBadRequest { 90 return nil, []error{&errortypes.BadInput{ 91 Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), 92 }} 93 } 94 95 if response.StatusCode != http.StatusOK { 96 return nil, []error{&errortypes.BadServerResponse{ 97 Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), 98 }} 99 } 100 var bidResp openrtb2.BidResponse 101 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 102 return nil, []error{&errortypes.BadServerResponse{ 103 Message: fmt.Sprintf("Bad server response: %d. ", err), 104 }} 105 } 106 107 var errs []error 108 bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) 109 110 for _, sb := range bidResp.SeatBid { 111 for i := range sb.Bid { 112 mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp) 113 if mediaType == openrtb_ext.BidTypeVideo { 114 price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64) 115 sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1) 116 } 117 118 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 119 Bid: &sb.Bid[i], 120 BidType: mediaType, 121 }) 122 } 123 } 124 125 return bidResponse, errs 126 } 127 128 func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) { 129 var bidderExt adapters.ExtImpBidder 130 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 131 err = &errortypes.BadInput{ 132 Message: err.Error(), 133 } 134 return nil, err 135 } 136 137 var gumgumExt openrtb_ext.ExtImpGumGum 138 if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { 139 err = &errortypes.BadInput{ 140 Message: err.Error(), 141 } 142 return nil, err 143 } 144 145 if imp.Banner != nil && imp.Banner.W == nil && imp.Banner.H == nil && len(imp.Banner.Format) > 0 { 146 bannerCopy := *imp.Banner 147 format := bannerCopy.Format[0] 148 bannerCopy.W = &(format.W) 149 bannerCopy.H = &(format.H) 150 151 if gumgumExt.Slot != 0 { 152 var err error 153 bannerExt := getBiggerFormat(bannerCopy.Format, gumgumExt.Slot) 154 bannerCopy.Ext, err = json.Marshal(&bannerExt) 155 if err != nil { 156 return nil, err 157 } 158 } 159 160 imp.Banner = &bannerCopy 161 } 162 163 if imp.Video != nil { 164 err := validateVideoParams(imp.Video) 165 if err != nil { 166 return nil, err 167 } 168 169 if gumgumExt.IrisID != "" { 170 videoCopy := *imp.Video 171 videoExt := openrtb_ext.ExtImpGumGumVideo{IrisID: gumgumExt.IrisID} 172 videoCopy.Ext, err = json.Marshal(&videoExt) 173 if err != nil { 174 return nil, err 175 } 176 imp.Video = &videoCopy 177 } 178 } 179 180 if gumgumExt.Product != "" { 181 var err error 182 imp.Ext, err = json.Marshal(map[string]string{"product": gumgumExt.Product}) 183 if err != nil { 184 return nil, err 185 } 186 } 187 188 return &gumgumExt, nil 189 } 190 191 func getBiggerFormat(formatList []openrtb2.Format, slot float64) openrtb_ext.ExtImpGumGumBanner { 192 maxw := int64(0) 193 maxh := int64(0) 194 greatestVal := int64(0) 195 for _, size := range formatList { 196 var biggerSide int64 197 if size.W > size.H { 198 biggerSide = size.W 199 } else { 200 biggerSide = size.H 201 } 202 203 if biggerSide > greatestVal || (biggerSide == greatestVal && size.W >= maxw && size.H >= maxh) { 204 greatestVal = biggerSide 205 maxh = size.H 206 maxw = size.W 207 } 208 } 209 210 bannerExt := openrtb_ext.ExtImpGumGumBanner{Si: slot, MaxW: float64(maxw), MaxH: float64(maxh)} 211 212 return bannerExt 213 } 214 215 func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { 216 for _, imp := range imps { 217 if imp.ID == impID && imp.Banner != nil { 218 return openrtb_ext.BidTypeBanner 219 } 220 } 221 return openrtb_ext.BidTypeVideo 222 } 223 224 func validateVideoParams(video *openrtb2.Video) (err error) { 225 if video.W == nil || *video.W == 0 || video.H == nil || *video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 { 226 return &errortypes.BadInput{ 227 Message: "Invalid or missing video field(s)", 228 } 229 } 230 return nil 231 } 232 233 // Builder builds a new instance of the GumGum adapter for the given bidder with the given config. 234 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 235 bidder := &adapter{ 236 URI: config.Endpoint, 237 } 238 return bidder, nil 239 }