github.com/prebid/prebid-server/v2@v2.18.0/adapters/limelightDigital/limelightDigital.go (about) 1 package limelightDigital 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 "strings" 9 "text/template" 10 11 "github.com/prebid/openrtb/v20/openrtb2" 12 13 "github.com/prebid/prebid-server/v2/adapters" 14 "github.com/prebid/prebid-server/v2/config" 15 "github.com/prebid/prebid-server/v2/errortypes" 16 "github.com/prebid/prebid-server/v2/macros" 17 "github.com/prebid/prebid-server/v2/openrtb_ext" 18 ) 19 20 type adapter struct { 21 endpointTemplate *template.Template 22 } 23 24 // Builder builds a new instance of the Limelight Digital adapter for the given bidder with the given config. 25 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 26 if config.Endpoint == "" { 27 return nil, errors.New("Endpoint adapter parameter is not provided") 28 } 29 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 30 if err != nil { 31 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 32 } 33 34 bidder := &adapter{ 35 endpointTemplate: template, 36 } 37 return bidder, nil 38 } 39 40 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 41 var requests []*adapters.RequestData 42 var errors []error 43 44 requestCopy := *request 45 for _, imp := range request.Imp { 46 limelightDigitalExt, err := getImpressionExt(&imp) 47 if err != nil { 48 errors = append(errors, err) 49 continue 50 } 51 52 url, err := a.buildEndpointURL(limelightDigitalExt) 53 if err != nil { 54 errors = append(errors, err) 55 continue 56 } 57 58 // Check if imp comes with bid floor amount defined in a foreign currency 59 if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != "USD" { 60 61 // Convert to US dollars 62 convertedValue, err := requestInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD") 63 if err != nil { 64 errors = append(errors, err) 65 continue 66 } 67 68 // Update after conversion. All imp elements inside request.Imp are shallow copies 69 // therefore, their non-pointer values are not shared memory and are safe to modify. 70 imp.BidFloorCur = "USD" 71 imp.BidFloor = convertedValue 72 } 73 74 requestCopy.ID = request.ID + "-" + imp.ID 75 requestCopy.Imp = []openrtb2.Imp{imp} 76 requestCopy.Ext = nil 77 78 requestJSON, err := json.Marshal(requestCopy) 79 if err != nil { 80 errors = append(errors, err) 81 } else { 82 requestData := &adapters.RequestData{ 83 Method: "POST", 84 Uri: url, 85 Body: requestJSON, 86 ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp), 87 } 88 requests = append(requests, requestData) 89 } 90 } 91 return requests, errors 92 } 93 94 func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { 95 if responseData.StatusCode == http.StatusNoContent { 96 return nil, nil 97 } 98 99 if responseData.StatusCode == http.StatusBadRequest { 100 err := &errortypes.BadInput{ 101 Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", 102 } 103 return nil, []error{err} 104 } 105 106 if responseData.StatusCode != http.StatusOK { 107 err := &errortypes.BadServerResponse{ 108 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), 109 } 110 return nil, []error{err} 111 } 112 113 if len(responseData.Body) == 0 { 114 return nil, nil 115 } 116 117 var response openrtb2.BidResponse 118 if err := json.Unmarshal(responseData.Body, &response); err != nil { 119 return nil, []error{err} 120 } 121 122 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) 123 bidResponse.Currency = response.Cur 124 var errs []error 125 for _, seatBid := range response.SeatBid { 126 for i, bid := range seatBid.Bid { 127 bidType, err := getMediaTypeForBid(bid.ImpID, request.Imp) 128 if err != nil { 129 errs = append(errs, err) 130 } else { 131 b := &adapters.TypedBid{ 132 Bid: &seatBid.Bid[i], 133 BidType: bidType, 134 } 135 bidResponse.Bids = append(bidResponse.Bids, b) 136 } 137 } 138 } 139 return bidResponse, errs 140 } 141 142 func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ImpExtLimelightDigital, error) { 143 var bidderExt adapters.ExtImpBidder 144 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 145 return nil, &errortypes.BadInput{ 146 Message: "ext.bidder is not provided", 147 } 148 } 149 var limelightDigitalExt openrtb_ext.ImpExtLimelightDigital 150 if err := json.Unmarshal(bidderExt.Bidder, &limelightDigitalExt); err != nil { 151 return nil, &errortypes.BadInput{ 152 Message: "ext.bidder is not provided", 153 } 154 } 155 imp.Ext = nil 156 return &limelightDigitalExt, nil 157 } 158 159 func (a *adapter) buildEndpointURL(params *openrtb_ext.ImpExtLimelightDigital) (string, error) { 160 endpointParams := macros.EndpointTemplateParams{ 161 Host: params.Host, 162 PublisherID: params.PublisherID.String(), 163 } 164 return macros.ResolveMacros(a.endpointTemplate, endpointParams) 165 } 166 167 func getMediaTypeForBid(impId string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { 168 for _, imp := range imps { 169 if imp.ID == impId { 170 if imp.Banner != nil { 171 return openrtb_ext.BidTypeBanner, nil 172 } else if imp.Video != nil { 173 return openrtb_ext.BidTypeVideo, nil 174 } else if imp.Audio != nil { 175 return openrtb_ext.BidTypeAudio, nil 176 } else if imp.Native != nil { 177 return openrtb_ext.BidTypeNative, nil 178 } 179 return "", fmt.Errorf("unknown media type of imp: %s", impId) 180 } 181 } 182 return "", fmt.Errorf("bid contains unknown imp id: %s", impId) 183 }