github.com/prebid/prebid-server@v0.275.0/adapters/operaads/operaads.go (about) 1 package operaads 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 "strings" 9 "text/template" 10 11 "github.com/prebid/prebid-server/adapters" 12 "github.com/prebid/prebid-server/config" 13 "github.com/prebid/prebid-server/errortypes" 14 "github.com/prebid/prebid-server/macros" 15 "github.com/prebid/prebid-server/openrtb_ext" 16 17 "github.com/prebid/openrtb/v19/openrtb2" 18 ) 19 20 type adapter struct { 21 epTemplate *template.Template 22 } 23 24 var ( 25 errBannerFormatMiss = errors.New("Size information missing for banner") 26 errDeviceOrOSMiss = errors.New("Impression is missing device OS information") 27 ) 28 29 // Builder builds a new instance of the operaads adapter for the given bidder with the given config. 30 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 31 epTemplate, err := template.New("endpoint").Parse(config.Endpoint) 32 if err != nil { 33 return nil, err 34 } 35 bidder := &adapter{ 36 epTemplate: epTemplate, 37 } 38 return bidder, nil 39 } 40 41 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 42 impCount := len(request.Imp) 43 requestData := make([]*adapters.RequestData, 0, impCount) 44 errs := []error{} 45 headers := http.Header{} 46 headers.Add("Content-Type", "application/json;charset=utf-8") 47 headers.Add("Accept", "application/json") 48 49 err := checkRequest(request) 50 if err != nil { 51 errs = append(errs, &errortypes.BadInput{ 52 Message: err.Error(), 53 }) 54 return nil, errs 55 } 56 57 for _, imp := range request.Imp { 58 var bidderExt adapters.ExtImpBidder 59 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 60 errs = append(errs, &errortypes.BadInput{ 61 Message: err.Error(), 62 }) 63 continue 64 } 65 var operaadsExt openrtb_ext.ImpExtOperaads 66 if err := json.Unmarshal(bidderExt.Bidder, &operaadsExt); err != nil { 67 errs = append(errs, &errortypes.BadInput{ 68 Message: err.Error(), 69 }) 70 continue 71 } 72 macro := macros.EndpointTemplateParams{PublisherID: operaadsExt.PublisherID, AccountID: operaadsExt.EndpointID} 73 endpoint, err := macros.ResolveMacros(a.epTemplate, ¯o) 74 if err != nil { 75 errs = append(errs, &errortypes.BadInput{ 76 Message: err.Error(), 77 }) 78 continue 79 } 80 imp.TagID = operaadsExt.PlacementID 81 formats := make([]interface{}, 0, 1) 82 if imp.Native != nil { 83 formats = append(formats, imp.Native) 84 } 85 if imp.Video != nil { 86 formats = append(formats, imp.Video) 87 } 88 if imp.Banner != nil { 89 formats = append(formats, imp.Banner) 90 } 91 for _, format := range formats { 92 req, err := flatImp(*request, imp, headers, endpoint, format) 93 if err != nil { 94 errs = append(errs, &errortypes.BadInput{ 95 Message: err.Error(), 96 }) 97 continue 98 } 99 if req != nil { 100 requestData = append(requestData, req) 101 } 102 } 103 } 104 return requestData, errs 105 } 106 107 func flatImp(requestCopy openrtb2.BidRequest, impCopy openrtb2.Imp, headers http.Header, endpoint string, format interface{}) (*adapters.RequestData, error) { 108 switch format.(type) { 109 case *openrtb2.Video: 110 impCopy.Native = nil 111 impCopy.Banner = nil 112 impCopy.ID = buildOperaImpId(impCopy.ID, openrtb_ext.BidTypeVideo) 113 case *openrtb2.Banner: 114 impCopy.Video = nil 115 impCopy.Native = nil 116 impCopy.ID = buildOperaImpId(impCopy.ID, openrtb_ext.BidTypeBanner) 117 case *openrtb2.Native: 118 impCopy.Video = nil 119 impCopy.Banner = nil 120 impCopy.ID = buildOperaImpId(impCopy.ID, openrtb_ext.BidTypeNative) 121 default: // do not need flat 122 return nil, nil 123 } 124 err := convertImpression(&impCopy) 125 if err != nil { 126 return nil, err 127 } 128 requestCopy.Imp = []openrtb2.Imp{impCopy} 129 reqJSON, err := json.Marshal(&requestCopy) 130 if err != nil { 131 return nil, err 132 } 133 return &adapters.RequestData{ 134 Method: http.MethodPost, 135 Uri: endpoint, 136 Body: reqJSON, 137 Headers: headers, 138 }, nil 139 } 140 141 func checkRequest(request *openrtb2.BidRequest) error { 142 if request.Device == nil || len(request.Device.OS) == 0 { 143 return errDeviceOrOSMiss 144 } 145 146 return nil 147 } 148 149 func convertImpression(imp *openrtb2.Imp) error { 150 if imp.Banner != nil { 151 bannerCopy, err := convertBanner(imp.Banner) 152 if err != nil { 153 return err 154 } 155 imp.Banner = bannerCopy 156 } 157 if imp.Native != nil && imp.Native.Request != "" { 158 v := make(map[string]interface{}) 159 err := json.Unmarshal([]byte(imp.Native.Request), &v) 160 if err != nil { 161 return err 162 } 163 _, ok := v["native"] 164 if !ok { 165 body, err := json.Marshal(struct { 166 Native interface{} `json:"native"` 167 }{ 168 Native: v, 169 }) 170 if err != nil { 171 return err 172 } 173 native := *imp.Native 174 native.Request = string(body) 175 imp.Native = &native 176 } 177 } 178 return nil 179 } 180 181 // make sure that banner has openrtb 2.3-compatible size information 182 func convertBanner(banner *openrtb2.Banner) (*openrtb2.Banner, error) { 183 if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 { 184 if len(banner.Format) > 0 { 185 f := banner.Format[0] 186 bannerCopy := *banner 187 bannerCopy.W = openrtb2.Int64Ptr(f.W) 188 bannerCopy.H = openrtb2.Int64Ptr(f.H) 189 return &bannerCopy, nil 190 } else { 191 return nil, errBannerFormatMiss 192 } 193 } 194 return banner, nil 195 } 196 197 func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 198 if response.StatusCode == http.StatusNoContent { 199 return nil, nil 200 } 201 202 if response.StatusCode == http.StatusBadRequest { 203 return nil, []error{&errortypes.BadInput{ 204 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 205 }} 206 } 207 208 if response.StatusCode != http.StatusOK { 209 return nil, []error{&errortypes.BadServerResponse{ 210 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 211 }} 212 } 213 214 var parsedResponse openrtb2.BidResponse 215 if err := json.Unmarshal(response.Body, &parsedResponse); err != nil { 216 return nil, []error{&errortypes.BadServerResponse{ 217 Message: err.Error(), 218 }} 219 } 220 221 bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) 222 for _, sb := range parsedResponse.SeatBid { 223 for i := 0; i < len(sb.Bid); i++ { 224 bid := sb.Bid[i] 225 if bid.Price != 0 { 226 var bidType openrtb_ext.BidType 227 bid.ImpID, bidType = parseOriginImpId(bid.ImpID) 228 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 229 Bid: &bid, 230 BidType: bidType, 231 }) 232 } 233 } 234 } 235 return bidResponse, nil 236 } 237 238 func buildOperaImpId(originId string, bidType openrtb_ext.BidType) string { 239 return strings.Join([]string{originId, "opa", string(bidType)}, ":") 240 } 241 242 func parseOriginImpId(impId string) (originId string, bidType openrtb_ext.BidType) { 243 items := strings.Split(impId, ":") 244 if len(items) < 2 { 245 return impId, "" 246 } 247 return strings.Join(items[:len(items)-2], ":"), openrtb_ext.BidType(items[len(items)-1]) 248 }