@@ -28,7 +28,8 @@ type HTTPRequestOptions[T any, R any] struct {
2828 Timeout time.Duration // Optional, defaults to 10 seconds
2929}
3030
31- // SendHTTPRequest is a generic HTTP request function that sends data and unmarshals the response
31+ // SendHTTPRequest is a legacy HTTP request function that uses msgpack encoding
32+ // Deprecated: Use SendHTTPRequestJSON for new implementations
3233func SendHTTPRequest [T any , R any ](opts HTTPRequestOptions [T , R ]) error {
3334 ctx , span := modelTracer .Start (opts .Context , "http.send" )
3435 defer span .End ()
@@ -118,3 +119,162 @@ func SendHTTPRequest[T any, R any](opts HTTPRequestOptions[T, R]) error {
118119
119120 return nil
120121}
122+
123+ // SendHTTPRequestJSON is a generic HTTP request function that sends JSON data and unmarshals the response
124+ func SendHTTPRequestJSON [T any , R any ](opts HTTPRequestOptions [T , R ]) error {
125+ ctx , span := modelTracer .Start (opts .Context , "http.send.json" )
126+ defer span .End ()
127+
128+ jsonData , err := json .Marshal (opts .Payload )
129+ if err != nil {
130+ logrus .Errorln (err )
131+ return err
132+ }
133+
134+ timeout := time .Second * 10
135+ if opts .Timeout > 0 {
136+ timeout = opts .Timeout
137+ }
138+
139+ client := & http.Client {
140+ Timeout : timeout ,
141+ Transport : otelhttp .NewTransport (http .DefaultTransport ),
142+ }
143+
144+ req , err := http .NewRequestWithContext (ctx , opts .Method , opts .Endpoint .APIEndpoint + opts .Path , bytes .NewBuffer (jsonData ))
145+ if err != nil {
146+ logrus .Errorln (err )
147+ return err
148+ }
149+
150+ contentType := "application/json"
151+ if opts .ContentType != "" {
152+ contentType = opts .ContentType
153+ }
154+
155+ req .Header .Set ("Content-Type" , contentType )
156+ req .Header .Set ("User-Agent" , fmt .Sprintf ("shelltimeCLI@%s" , commitID ))
157+ req .Header .Set ("Authorization" , "CLI " + opts .Endpoint .Token )
158+
159+ logrus .Traceln ("http: " , req .URL .String ())
160+
161+ resp , err := client .Do (req )
162+ if err != nil {
163+ logrus .Errorln (err )
164+ return err
165+ }
166+ defer resp .Body .Close ()
167+
168+ logrus .Traceln ("http: " , resp .Status )
169+
170+ if resp .StatusCode == http .StatusNoContent {
171+ return nil
172+ }
173+
174+ buf , err := io .ReadAll (resp .Body )
175+ if err != nil {
176+ logrus .Errorln (err )
177+ return err
178+ }
179+
180+ if resp .StatusCode != http .StatusOK {
181+ var msg errorResponse
182+ err = json .Unmarshal (buf , & msg )
183+ if err != nil {
184+ logrus .Errorln ("Failed to parse error response:" , err )
185+ return fmt .Errorf ("HTTP error: %d" , resp .StatusCode )
186+ }
187+ logrus .Errorln ("Error response:" , msg .ErrorMessage )
188+ return errors .New (msg .ErrorMessage )
189+ }
190+
191+ // Only try to unmarshal if we have a response struct
192+ if opts .Response != nil {
193+ err = json .Unmarshal (buf , opts .Response )
194+ if err != nil {
195+ logrus .Errorln ("Failed to unmarshal JSON response:" , err )
196+ return err
197+ }
198+ }
199+
200+ return nil
201+ }
202+
203+ // GraphQLResponse is a generic wrapper for GraphQL responses
204+ type GraphQLResponse [T any ] struct {
205+ Data T `json:"data"`
206+ Errors []GraphQLError `json:"errors,omitempty"`
207+ }
208+
209+ // GraphQLError represents a GraphQL error
210+ type GraphQLError struct {
211+ Message string `json:"message"`
212+ Extensions map [string ]interface {} `json:"extensions,omitempty"`
213+ Path []interface {} `json:"path,omitempty"`
214+ }
215+
216+ // GraphQLRequestOptions contains options for GraphQL requests
217+ type GraphQLRequestOptions [R any ] struct {
218+ Context context.Context
219+ Endpoint Endpoint
220+ Query string
221+ Variables map [string ]interface {}
222+ Response * R
223+ Timeout time.Duration // Optional, defaults to 30 seconds
224+ }
225+
226+ // SendGraphQLRequest sends a GraphQL request and unmarshals the response
227+ func SendGraphQLRequest [R any ](opts GraphQLRequestOptions [R ]) error {
228+ ctx , span := modelTracer .Start (opts .Context , "graphql.send" )
229+ defer span .End ()
230+
231+ // Build GraphQL payload
232+ payload := map [string ]interface {}{
233+ "query" : opts .Query ,
234+ }
235+ if opts .Variables != nil {
236+ payload ["variables" ] = opts .Variables
237+ }
238+
239+ // Build GraphQL endpoint path
240+ graphQLPath := "/api/v2/graphql"
241+
242+ // Set default timeout
243+ timeout := time .Second * 30
244+ if opts .Timeout > 0 {
245+ timeout = opts .Timeout
246+ }
247+
248+ // Use the new JSON HTTP request function
249+ err := SendHTTPRequestJSON (HTTPRequestOptions [map [string ]interface {}, R ]{
250+ Context : ctx ,
251+ Endpoint : opts .Endpoint ,
252+ Method : http .MethodPost ,
253+ Path : graphQLPath ,
254+ Payload : payload ,
255+ Response : opts .Response ,
256+ Timeout : timeout ,
257+ })
258+
259+ if err != nil {
260+ // The error is already formatted by SendHTTPRequestJSON
261+ return err
262+ }
263+
264+ // Check for GraphQL errors in the response if we have a response
265+ if opts .Response != nil {
266+ // Marshal response back to check for errors
267+ respBytes , err := json .Marshal (opts .Response )
268+ if err == nil {
269+ var errorCheck struct {
270+ Errors []GraphQLError `json:"errors,omitempty"`
271+ }
272+ if err := json .Unmarshal (respBytes , & errorCheck ); err == nil && len (errorCheck .Errors ) > 0 {
273+ // Return the first error message if there are GraphQL errors
274+ return fmt .Errorf ("GraphQL error: %s" , errorCheck .Errors [0 ].Message )
275+ }
276+ }
277+ }
278+
279+ return nil
280+ }
0 commit comments