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