github.com/prebid/prebid-server/v2@v2.18.0/adapters/lunamedia/lunamedia.go (about) 1 package lunamedia 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "text/template" 8 9 "github.com/prebid/openrtb/v20/openrtb2" 10 "github.com/prebid/prebid-server/v2/adapters" 11 "github.com/prebid/prebid-server/v2/config" 12 "github.com/prebid/prebid-server/v2/errortypes" 13 "github.com/prebid/prebid-server/v2/macros" 14 "github.com/prebid/prebid-server/v2/openrtb_ext" 15 ) 16 17 type LunaMediaAdapter struct { 18 EndpointTemplate *template.Template 19 } 20 21 // MakeRequests prepares request information for prebid-server core 22 func (adapter *LunaMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 23 errs := make([]error, 0, len(request.Imp)) 24 if len(request.Imp) == 0 { 25 errs = append(errs, &errortypes.BadInput{Message: "No impression in the bid request"}) 26 return nil, errs 27 } 28 pub2impressions, imps, err := getImpressionsInfo(request.Imp) 29 if len(imps) == 0 { 30 return nil, err 31 } 32 errs = append(errs, err...) 33 34 if len(pub2impressions) == 0 { 35 return nil, errs 36 } 37 38 result := make([]*adapters.RequestData, 0, len(pub2impressions)) 39 for k, imps := range pub2impressions { 40 bidRequest, err := adapter.buildAdapterRequest(request, &k, imps) 41 if err != nil { 42 errs = append(errs, err) 43 return nil, errs 44 } else { 45 result = append(result, bidRequest) 46 } 47 } 48 return result, errs 49 } 50 51 // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts 52 func getImpressionsInfo(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpLunaMedia][]openrtb2.Imp, []openrtb2.Imp, []error) { 53 errors := make([]error, 0, len(imps)) 54 resImps := make([]openrtb2.Imp, 0, len(imps)) 55 res := make(map[openrtb_ext.ExtImpLunaMedia][]openrtb2.Imp) 56 57 for _, imp := range imps { 58 impExt, err := getImpressionExt(&imp) 59 if err != nil { 60 errors = append(errors, err) 61 continue 62 } 63 if err := validateImpression(impExt); err != nil { 64 errors = append(errors, err) 65 continue 66 } 67 //dispatchImpressions 68 //Group impressions by LunaMedia-specific parameters `pubid 69 if err := compatImpression(&imp); err != nil { 70 errors = append(errors, err) 71 continue 72 } 73 if res[*impExt] == nil { 74 res[*impExt] = make([]openrtb2.Imp, 0) 75 } 76 res[*impExt] = append(res[*impExt], imp) 77 resImps = append(resImps, imp) 78 } 79 return res, resImps, errors 80 } 81 82 func validateImpression(impExt *openrtb_ext.ExtImpLunaMedia) error { 83 if impExt.PublisherID == "" { 84 return &errortypes.BadInput{Message: "No pubid value provided"} 85 } 86 return nil 87 } 88 89 // Alter impression info to comply with LunaMedia platform requirements 90 func compatImpression(imp *openrtb2.Imp) error { 91 imp.Ext = nil //do not forward ext to LunaMedia platform 92 if imp.Banner != nil { 93 return compatBannerImpression(imp) 94 } 95 return nil 96 } 97 98 func compatBannerImpression(imp *openrtb2.Imp) error { 99 // Create a copy of the banner, since imp is a shallow copy of the original. 100 101 bannerCopy := *imp.Banner 102 banner := &bannerCopy 103 //As banner.w/h are required fields for LunaMedia platform - take the first format entry 104 if banner.W == nil || banner.H == nil { 105 if len(banner.Format) == 0 { 106 return &errortypes.BadInput{Message: "Expected at least one banner.format entry or explicit w/h"} 107 } 108 format := banner.Format[0] 109 banner.Format = banner.Format[1:] 110 banner.W = &format.W 111 banner.H = &format.H 112 imp.Banner = banner 113 } 114 return nil 115 } 116 117 func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpLunaMedia, error) { 118 var bidderExt adapters.ExtImpBidder 119 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 120 return nil, &errortypes.BadInput{ 121 Message: err.Error(), 122 } 123 } 124 var LunaMediaExt openrtb_ext.ExtImpLunaMedia 125 if err := json.Unmarshal(bidderExt.Bidder, &LunaMediaExt); err != nil { 126 return nil, &errortypes.BadInput{ 127 Message: err.Error(), 128 } 129 } 130 return &LunaMediaExt, nil 131 } 132 133 func (adapter *LunaMediaAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb2.Imp) (*adapters.RequestData, error) { 134 newBidRequest := createBidRequest(prebidBidRequest, params, imps) 135 reqJSON, err := json.Marshal(newBidRequest) 136 if err != nil { 137 return nil, err 138 } 139 140 headers := http.Header{} 141 headers.Add("Content-Type", "application/json;charset=utf-8") 142 headers.Add("Accept", "application/json") 143 headers.Add("x-openrtb-version", "2.5") 144 145 url, err := adapter.buildEndpointURL(params) 146 if err != nil { 147 return nil, err 148 } 149 150 return &adapters.RequestData{ 151 Method: "POST", 152 Uri: url, 153 Body: reqJSON, 154 Headers: headers, 155 ImpIDs: openrtb_ext.GetImpIDs(imps)}, nil 156 } 157 158 func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb2.Imp) *openrtb2.BidRequest { 159 bidRequest := *prebidBidRequest 160 bidRequest.Imp = imps 161 for idx := range bidRequest.Imp { 162 imp := &bidRequest.Imp[idx] 163 imp.TagID = params.Placement 164 } 165 if bidRequest.Site != nil { 166 // Need to copy Site as Request is a shallow copy 167 siteCopy := *bidRequest.Site 168 bidRequest.Site = &siteCopy 169 bidRequest.Site.Publisher = nil 170 bidRequest.Site.Domain = "" 171 } 172 if bidRequest.App != nil { 173 // Need to copy App as Request is a shallow copy 174 appCopy := *bidRequest.App 175 bidRequest.App = &appCopy 176 bidRequest.App.Publisher = nil 177 } 178 return &bidRequest 179 } 180 181 // Builds enpoint url based on adapter-specific pub settings from imp.ext 182 func (adapter *LunaMediaAdapter) buildEndpointURL(params *openrtb_ext.ExtImpLunaMedia) (string, error) { 183 endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherID} 184 return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) 185 } 186 187 // MakeBids translates LunaMedia bid response to prebid-server specific format 188 func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 189 var msg = "" 190 if response.StatusCode == http.StatusNoContent { 191 return nil, nil 192 } 193 if response.StatusCode != http.StatusOK { 194 msg = fmt.Sprintf("Unexpected http status code: %d", response.StatusCode) 195 return nil, []error{&errortypes.BadServerResponse{Message: msg}} 196 197 } 198 var bidResp openrtb2.BidResponse 199 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 200 msg = fmt.Sprintf("Bad server response: %d", err) 201 return nil, []error{&errortypes.BadServerResponse{Message: msg}} 202 } 203 if len(bidResp.SeatBid) != 1 { 204 var msg = fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid)) 205 return nil, []error{&errortypes.BadServerResponse{Message: msg}} 206 } 207 208 seatBid := bidResp.SeatBid[0] 209 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) 210 211 for i := 0; i < len(seatBid.Bid); i++ { 212 bid := seatBid.Bid[i] 213 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 214 Bid: &bid, 215 BidType: getMediaTypeForImpID(bid.ImpID, internalRequest.Imp), 216 }) 217 } 218 return bidResponse, nil 219 } 220 221 // getMediaTypeForImp figures out which media type this bid is for 222 func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { 223 for _, imp := range imps { 224 if imp.ID == impID && imp.Video != nil { 225 return openrtb_ext.BidTypeVideo 226 } 227 } 228 return openrtb_ext.BidTypeBanner 229 } 230 231 // Builder builds a new instance of the LunaMedia adapter for the given bidder with the given config. 232 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 233 urlTemplate, err := template.New("endpointTemplate").Parse(config.Endpoint) 234 if err != nil { 235 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 236 } 237 238 bidder := &LunaMediaAdapter{ 239 EndpointTemplate: urlTemplate, 240 } 241 return bidder, nil 242 }