github.com/prebid/prebid-server@v0.275.0/adapters/smartrtb/smartrtb.go (about) 1 package smartrtb 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "text/template" 8 9 "github.com/prebid/openrtb/v19/openrtb2" 10 "github.com/prebid/prebid-server/adapters" 11 "github.com/prebid/prebid-server/config" 12 "github.com/prebid/prebid-server/errortypes" 13 "github.com/prebid/prebid-server/macros" 14 "github.com/prebid/prebid-server/openrtb_ext" 15 ) 16 17 // Base adapter structure. 18 type SmartRTBAdapter struct { 19 EndpointTemplate *template.Template 20 } 21 22 // Bid request extension appended to downstream request. 23 // PubID are non-empty iff request.{App,Site} or 24 // request.{App,Site}.Publisher are nil, respectively. 25 type bidRequestExt struct { 26 PubID string `json:"pub_id,omitempty"` 27 ZoneID string `json:"zone_id,omitempty"` 28 ForceBid bool `json:"force_bid,omitempty"` 29 } 30 31 // bidExt.CreativeType values. 32 const ( 33 creativeTypeBanner string = "BANNER" 34 creativeTypeVideo = "VIDEO" 35 creativeTypeNative = "NATIVE" 36 creativeTypeAudio = "AUDIO" 37 ) 38 39 // Bid response extension from downstream. 40 type bidExt struct { 41 CreativeType string `json:"format"` 42 } 43 44 // Builder builds a new instance of the SmartRTB adapter for the given bidder with the given config. 45 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 46 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 47 if err != nil { 48 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 49 } 50 51 bidder := &SmartRTBAdapter{ 52 EndpointTemplate: template, 53 } 54 return bidder, nil 55 } 56 57 func (adapter *SmartRTBAdapter) buildEndpointURL(pubID string) (string, error) { 58 endpointParams := macros.EndpointTemplateParams{PublisherID: pubID} 59 return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) 60 } 61 62 func parseExtImp(dst *bidRequestExt, imp *openrtb2.Imp) error { 63 var ext adapters.ExtImpBidder 64 if err := json.Unmarshal(imp.Ext, &ext); err != nil { 65 return &errortypes.BadInput{ 66 Message: err.Error(), 67 } 68 } 69 70 var src openrtb_ext.ExtImpSmartRTB 71 if err := json.Unmarshal(ext.Bidder, &src); err != nil { 72 return &errortypes.BadInput{ 73 Message: err.Error(), 74 } 75 } 76 77 if dst.PubID == "" { 78 dst.PubID = src.PubID 79 } 80 81 if src.ZoneID != "" { 82 imp.TagID = src.ZoneID 83 } 84 return nil 85 } 86 87 func (s *SmartRTBAdapter) MakeRequests(brq *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 88 var imps []openrtb2.Imp 89 var err error 90 ext := bidRequestExt{} 91 nrImps := len(brq.Imp) 92 errs := make([]error, 0, nrImps) 93 94 for i := 0; i < nrImps; i++ { 95 imp := brq.Imp[i] 96 if imp.Banner == nil && imp.Video == nil { 97 continue 98 } 99 100 err = parseExtImp(&ext, &imp) 101 if err != nil { 102 errs = append(errs, err) 103 continue 104 } 105 106 imps = append(imps, imp) 107 } 108 109 if len(imps) == 0 { 110 return nil, errs 111 } 112 113 if ext.PubID == "" { 114 return nil, append(errs, &errortypes.BadInput{Message: "Cannot infer publisher ID from bid ext"}) 115 } 116 117 brq.Ext, err = json.Marshal(ext) 118 if err != nil { 119 return nil, append(errs, err) 120 } 121 122 brq.Imp = imps 123 124 rq, err := json.Marshal(brq) 125 if err != nil { 126 return nil, append(errs, err) 127 } 128 129 url, err := s.buildEndpointURL(ext.PubID) 130 if err != nil { 131 return nil, append(errs, err) 132 } 133 134 headers := http.Header{} 135 headers.Add("Content-Type", "application/json;charset=utf-8") 136 headers.Add("Accept", "application/json") 137 headers.Add("x-openrtb-version", "2.5") 138 return []*adapters.RequestData{{ 139 Method: "POST", 140 Uri: url, 141 Body: rq, 142 Headers: headers, 143 }}, errs 144 } 145 146 func (s *SmartRTBAdapter) MakeBids( 147 brq *openrtb2.BidRequest, drq *adapters.RequestData, 148 rs *adapters.ResponseData, 149 ) (*adapters.BidderResponse, []error) { 150 if rs.StatusCode == http.StatusNoContent { 151 return nil, nil 152 } else if rs.StatusCode == http.StatusBadRequest { 153 return nil, []error{&errortypes.BadInput{Message: "Invalid request."}} 154 } else if rs.StatusCode != http.StatusOK { 155 return nil, []error{&errortypes.BadServerResponse{ 156 Message: fmt.Sprintf("Unexpected HTTP status %d.", rs.StatusCode), 157 }} 158 } 159 160 var brs openrtb2.BidResponse 161 if err := json.Unmarshal(rs.Body, &brs); err != nil { 162 return nil, []error{err} 163 } 164 165 rv := adapters.NewBidderResponseWithBidsCapacity(5) 166 for _, seat := range brs.SeatBid { 167 for i := range seat.Bid { 168 var ext bidExt 169 if err := json.Unmarshal(seat.Bid[i].Ext, &ext); err != nil { 170 return nil, []error{&errortypes.BadServerResponse{ 171 Message: "Invalid bid extension from endpoint.", 172 }} 173 } 174 175 var btype openrtb_ext.BidType 176 switch ext.CreativeType { 177 case creativeTypeBanner: 178 btype = openrtb_ext.BidTypeBanner 179 case creativeTypeVideo: 180 btype = openrtb_ext.BidTypeVideo 181 default: 182 return nil, []error{&errortypes.BadServerResponse{ 183 Message: fmt.Sprintf("Unsupported creative type %s.", 184 ext.CreativeType), 185 }} 186 } 187 188 seat.Bid[i].Ext = nil 189 190 rv.Bids = append(rv.Bids, &adapters.TypedBid{ 191 Bid: &seat.Bid[i], 192 BidType: btype, 193 }) 194 } 195 } 196 return rv, nil 197 }