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