github.com/prebid/prebid-server@v0.275.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/v19/openrtb2" 12 13 "github.com/prebid/prebid-server/adapters" 14 "github.com/prebid/prebid-server/config" 15 "github.com/prebid/prebid-server/errortypes" 16 "github.com/prebid/prebid-server/macros" 17 "github.com/prebid/prebid-server/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 } 87 requests = append(requests, requestData) 88 } 89 } 90 return requests, errors 91 } 92 93 func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { 94 if responseData.StatusCode == http.StatusNoContent { 95 return nil, nil 96 } 97 98 if responseData.StatusCode == http.StatusBadRequest { 99 err := &errortypes.BadInput{ 100 Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", 101 } 102 return nil, []error{err} 103 } 104 105 if responseData.StatusCode != http.StatusOK { 106 err := &errortypes.BadServerResponse{ 107 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), 108 } 109 return nil, []error{err} 110 } 111 112 if len(responseData.Body) == 0 { 113 return nil, nil 114 } 115 116 var response openrtb2.BidResponse 117 if err := json.Unmarshal(responseData.Body, &response); err != nil { 118 return nil, []error{err} 119 } 120 121 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) 122 bidResponse.Currency = response.Cur 123 var errs []error 124 for _, seatBid := range response.SeatBid { 125 for i, bid := range seatBid.Bid { 126 bidType, err := getMediaTypeForBid(bid.ImpID, request.Imp) 127 if err != nil { 128 errs = append(errs, err) 129 } else { 130 b := &adapters.TypedBid{ 131 Bid: &seatBid.Bid[i], 132 BidType: bidType, 133 } 134 bidResponse.Bids = append(bidResponse.Bids, b) 135 } 136 } 137 } 138 return bidResponse, errs 139 } 140 141 func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ImpExtLimelightDigital, error) { 142 var bidderExt adapters.ExtImpBidder 143 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 144 return nil, &errortypes.BadInput{ 145 Message: "ext.bidder is not provided", 146 } 147 } 148 var limelightDigitalExt openrtb_ext.ImpExtLimelightDigital 149 if err := json.Unmarshal(bidderExt.Bidder, &limelightDigitalExt); err != nil { 150 return nil, &errortypes.BadInput{ 151 Message: "ext.bidder is not provided", 152 } 153 } 154 imp.Ext = nil 155 return &limelightDigitalExt, nil 156 } 157 158 func (a *adapter) buildEndpointURL(params *openrtb_ext.ImpExtLimelightDigital) (string, error) { 159 endpointParams := macros.EndpointTemplateParams{ 160 Host: params.Host, 161 PublisherID: params.PublisherID.String(), 162 } 163 return macros.ResolveMacros(a.endpointTemplate, endpointParams) 164 } 165 166 func getMediaTypeForBid(impId string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { 167 for _, imp := range imps { 168 if imp.ID == impId { 169 if imp.Banner != nil { 170 return openrtb_ext.BidTypeBanner, nil 171 } else if imp.Video != nil { 172 return openrtb_ext.BidTypeVideo, nil 173 } else if imp.Audio != nil { 174 return openrtb_ext.BidTypeAudio, nil 175 } else if imp.Native != nil { 176 return openrtb_ext.BidTypeNative, nil 177 } 178 return "", fmt.Errorf("unknown media type of imp: %s", impId) 179 } 180 } 181 return "", fmt.Errorf("bid contains unknown imp id: %s", impId) 182 }