github.com/prebid/prebid-server/v2@v2.18.0/adapters/teads/teads.go (about) 1 package teads 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "strconv" 9 "text/template" 10 11 "github.com/prebid/openrtb/v20/openrtb2" 12 "github.com/prebid/prebid-server/v2/adapters" 13 "github.com/prebid/prebid-server/v2/config" 14 "github.com/prebid/prebid-server/v2/errortypes" 15 "github.com/prebid/prebid-server/v2/macros" 16 "github.com/prebid/prebid-server/v2/openrtb_ext" 17 ) 18 19 // Builder builds a new instance of the Teads adapter for the given bidder with the given config. 20 func Builder(_ openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 21 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 22 if err != nil { 23 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 24 } 25 26 bidder := &adapter{ 27 endpointTemplate: template, 28 } 29 return bidder, nil 30 } 31 32 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 33 if len(request.Imp) == 0 { 34 return nil, []error{&errortypes.BadInput{ 35 Message: "No impression in the bid request", 36 }} 37 } 38 39 endpointURL, err := a.buildEndpointURL() 40 if endpointURL == "" { 41 return nil, []error{err} 42 } 43 44 if err := updateImpObject(request.Imp); err != nil { 45 return nil, []error{err} 46 } 47 48 reqJSON, err := json.Marshal(request) 49 if err != nil { 50 return nil, []error{&errortypes.BadInput{ 51 Message: "Error parsing BidRequest object", 52 }} 53 } 54 55 headers := http.Header{} 56 headers.Add("Content-Type", "application/json;charset=utf-8") 57 return []*adapters.RequestData{{ 58 Method: "POST", 59 Uri: endpointURL, 60 Body: reqJSON, 61 Headers: headers, 62 ImpIDs: openrtb_ext.GetImpIDs(request.Imp), 63 }}, []error{} 64 } 65 66 func updateImpObject(imps []openrtb2.Imp) error { 67 for i := range imps { 68 imp := &imps[i] 69 70 if imp.Banner != nil { 71 if len(imp.Banner.Format) != 0 { 72 bannerCopy := *imp.Banner 73 bannerCopy.H = &imp.Banner.Format[0].H 74 bannerCopy.W = &imp.Banner.Format[0].W 75 imp.Banner = &bannerCopy 76 } 77 } 78 79 var defaultImpExt defaultBidderImpExtension 80 if err := json.Unmarshal(imp.Ext, &defaultImpExt); err != nil { 81 return &errortypes.BadInput{ 82 Message: "Error parsing Imp.Ext object", 83 } 84 } 85 if defaultImpExt.Bidder.PlacementId == 0 { 86 return &errortypes.BadInput{ 87 Message: "placementId should not be 0.", 88 } 89 } 90 imp.TagID = strconv.Itoa(defaultImpExt.Bidder.PlacementId) 91 teadsImpExt := &teadsImpExtension{ 92 KV: teadsKV{ 93 PlacementId: defaultImpExt.Bidder.PlacementId, 94 }, 95 } 96 if extJson, err := json.Marshal(teadsImpExt); err != nil { 97 return &errortypes.BadInput{ 98 Message: "Error stringify Imp.Ext object", 99 } 100 } else { 101 imp.Ext = extJson 102 } 103 } 104 return nil 105 } 106 107 // Builds enpoint url based on adapter-specific pub settings from imp.ext 108 func (a *adapter) buildEndpointURL() (string, error) { 109 endpointParams := macros.EndpointTemplateParams{} 110 host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams) 111 112 if err != nil { 113 return "", &errortypes.BadInput{ 114 Message: "Unable to parse endpoint url template: " + err.Error(), 115 } 116 } 117 118 thisURI, err := url.Parse(host) 119 if err != nil { 120 return "", &errortypes.BadInput{ 121 Message: "Malformed URL: " + err.Error(), 122 } 123 } 124 125 return thisURI.String(), nil 126 } 127 128 func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 129 if adapters.IsResponseStatusCodeNoContent(response) { 130 return nil, nil 131 } 132 133 err := adapters.CheckResponseStatusCodeForErrors(response) 134 if err != nil { 135 return nil, []error{err} 136 } 137 138 var bidResp openrtb2.BidResponse 139 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 140 return nil, []error{err} 141 } 142 143 bidderResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid)) 144 145 for _, sb := range bidResp.SeatBid { 146 for i := 0; i < len(sb.Bid); i++ { 147 bid := sb.Bid[i] 148 149 bidExtTeads, err := getTeadsRendererFromBidExt(bid.Ext) 150 if err != nil { 151 return nil, err 152 } 153 bidType, err := getMediaTypeForImp(bid.ImpID, internalRequest.Imp) 154 if err != nil { 155 return nil, err 156 } 157 bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ 158 Bid: &bid, 159 BidMeta: &openrtb_ext.ExtBidPrebidMeta{ 160 RendererName: bidExtTeads.Prebid.Meta.RendererName, 161 RendererVersion: bidExtTeads.Prebid.Meta.RendererVersion, 162 }, 163 BidType: bidType, 164 }) 165 } 166 } 167 if bidResp.Cur != "" { 168 bidderResponse.Currency = bidResp.Cur 169 } 170 return bidderResponse, nil 171 } 172 173 func getTeadsRendererFromBidExt(ext json.RawMessage) (*teadsBidExt, []error) { 174 var bidExtTeads teadsBidExt 175 if err := json.Unmarshal(ext, &bidExtTeads); err != nil { 176 return nil, []error{err} 177 } 178 if bidExtTeads.Prebid.Meta.RendererName == "" { 179 return nil, []error{&errortypes.BadInput{ 180 Message: "RendererName should not be empty if present", 181 }} 182 } 183 if bidExtTeads.Prebid.Meta.RendererVersion == "" { 184 return nil, []error{&errortypes.BadInput{ 185 Message: "RendererVersion should not be empty if present", 186 }} 187 } 188 return &bidExtTeads, nil 189 } 190 191 func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, []error) { 192 for _, imp := range imps { 193 if imp.ID == impID { 194 if imp.Video != nil { 195 return openrtb_ext.BidTypeVideo, nil 196 } 197 return openrtb_ext.BidTypeBanner, nil 198 } 199 } 200 return openrtb_ext.BidType(""), []error{&errortypes.BadInput{ 201 Message: "Imp ids were not equals", 202 }} 203 }