github.com/prebid/prebid-server/v2@v2.18.0/adapters/alkimi/alkimi.go (about) 1 package alkimi 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "strconv" 9 "strings" 10 11 "github.com/prebid/prebid-server/v2/errortypes" 12 "github.com/prebid/prebid-server/v2/floors" 13 14 "github.com/prebid/openrtb/v20/openrtb2" 15 "github.com/prebid/prebid-server/v2/adapters" 16 "github.com/prebid/prebid-server/v2/config" 17 "github.com/prebid/prebid-server/v2/openrtb_ext" 18 ) 19 20 const price_macro = "${AUCTION_PRICE}" 21 22 type adapter struct { 23 endpoint string 24 } 25 26 type extObj struct { 27 AlkimiBidderExt openrtb_ext.ExtImpAlkimi `json:"bidder"` 28 } 29 30 // Builder builds a new instance of the Alkimi adapter for the given bidder with the given config. 31 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 32 endpointURL, err := url.Parse(config.Endpoint) 33 if err != nil || len(endpointURL.String()) == 0 { 34 return nil, fmt.Errorf("invalid endpoint: %v", err) 35 } 36 37 bidder := &adapter{ 38 endpoint: endpointURL.String(), 39 } 40 return bidder, nil 41 } 42 43 // MakeRequests creates Alkimi adapter requests 44 func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { 45 reqCopy := *request 46 47 updated, errs := updateImps(reqCopy) 48 if len(errs) > 0 || len(reqCopy.Imp) != len(updated) { 49 return nil, errs 50 } 51 52 reqCopy.Imp = updated 53 encoded, err := json.Marshal(reqCopy) 54 if err != nil { 55 errs = append(errs, err) 56 } else { 57 reqBidder := buildBidderRequest(adapter, encoded, openrtb_ext.GetImpIDs(reqCopy.Imp)) 58 reqsBidder = append(reqsBidder, reqBidder) 59 } 60 return 61 } 62 63 func updateImps(bidRequest openrtb2.BidRequest) ([]openrtb2.Imp, []error) { 64 var errs []error 65 66 updatedImps := make([]openrtb2.Imp, 0, len(bidRequest.Imp)) 67 for _, imp := range bidRequest.Imp { 68 69 var bidderExt adapters.ExtImpBidder 70 var extImpAlkimi openrtb_ext.ExtImpAlkimi 71 72 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 73 errs = append(errs, err) 74 continue 75 } 76 77 if err := json.Unmarshal(bidderExt.Bidder, &extImpAlkimi); err != nil { 78 errs = append(errs, err) 79 continue 80 } 81 82 var bidFloorPrice floors.Price 83 bidFloorPrice.FloorMinCur = imp.BidFloorCur 84 bidFloorPrice.FloorMin = imp.BidFloor 85 86 if len(bidFloorPrice.FloorMinCur) > 0 && bidFloorPrice.FloorMin > 0 { 87 imp.BidFloor = bidFloorPrice.FloorMin 88 } else { 89 imp.BidFloor = extImpAlkimi.BidFloor 90 } 91 imp.Instl = extImpAlkimi.Instl 92 imp.Exp = extImpAlkimi.Exp 93 94 temp := extObj{AlkimiBidderExt: extImpAlkimi} 95 temp.AlkimiBidderExt.AdUnitCode = imp.ID 96 97 extJson, err := json.Marshal(temp) 98 if err != nil { 99 errs = append(errs, err) 100 continue 101 } 102 imp.Ext = extJson 103 updatedImps = append(updatedImps, imp) 104 } 105 106 return updatedImps, errs 107 } 108 109 func buildBidderRequest(adapter *adapter, encoded []byte, impIDs []string) *adapters.RequestData { 110 headers := http.Header{} 111 headers.Add("Content-Type", "application/json;charset=utf-8") 112 headers.Add("Accept", "application/json") 113 114 reqBidder := &adapters.RequestData{ 115 Method: "POST", 116 Uri: adapter.endpoint, 117 Body: encoded, 118 Headers: headers, 119 ImpIDs: impIDs, 120 } 121 return reqBidder 122 } 123 124 // MakeBids will parse the bids from the Alkimi server 125 func (adapter *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 126 var errs []error 127 128 if adapters.IsResponseStatusCodeNoContent(response) { 129 return nil, nil 130 } 131 132 if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { 133 return nil, []error{err} 134 } 135 136 var bidResp openrtb2.BidResponse 137 err := json.Unmarshal(response.Body, &bidResp) 138 if err != nil { 139 return nil, []error{err} 140 } 141 142 seatBidCount := len(bidResp.SeatBid) 143 if seatBidCount == 0 { 144 return nil, []error{&errortypes.BadServerResponse{ 145 Message: "Empty SeatBid array", 146 }} 147 } 148 149 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) 150 for _, seatBid := range bidResp.SeatBid { 151 for _, bid := range seatBid.Bid { 152 copyBid := bid 153 resolveMacros(©Bid) 154 impId := copyBid.ImpID 155 imp := request.Imp 156 bidType, err := getMediaTypeForImp(impId, imp) 157 if err != nil { 158 errs = append(errs, err) 159 continue 160 } 161 bidderBid := &adapters.TypedBid{ 162 Bid: ©Bid, 163 BidType: bidType, 164 } 165 bidResponse.Bids = append(bidResponse.Bids, bidderBid) 166 } 167 } 168 return bidResponse, errs 169 } 170 171 func resolveMacros(bid *openrtb2.Bid) { 172 strPrice := strconv.FormatFloat(bid.Price, 'f', -1, 64) 173 bid.NURL = strings.Replace(bid.NURL, price_macro, strPrice, -1) 174 bid.AdM = strings.Replace(bid.AdM, price_macro, strPrice, -1) 175 } 176 177 func getMediaTypeForImp(impId string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { 178 for _, imp := range imps { 179 if imp.ID == impId { 180 if imp.Banner != nil { 181 return openrtb_ext.BidTypeBanner, nil 182 } 183 if imp.Video != nil { 184 return openrtb_ext.BidTypeVideo, nil 185 } 186 if imp.Audio != nil { 187 return openrtb_ext.BidTypeAudio, nil 188 } 189 } 190 } 191 return "", &errortypes.BadInput{ 192 Message: fmt.Sprintf("Failed to find imp \"%s\"", impId), 193 } 194 }