• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

UiPath / uipathcli / 13832257596

13 Mar 2025 10:19AM UTC coverage: 90.57% (+0.4%) from 90.121%
13832257596

push

github

web-flow
Merge pull request #156 from UiPath/feature/network-package

Add network package with common http client

478 of 505 new or added lines in 26 files covered. (94.65%)

10 existing lines in 6 files now uncovered.

5369 of 5928 relevant lines covered (90.57%)

1.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.83
/executor/http_executor.go
1
package executor
2

3
import (
4
        "bytes"
5
        "context"
6
        "encoding/json"
7
        "errors"
8
        "fmt"
9
        "io"
10
        "mime/multipart"
11
        "net/http"
12
        "net/url"
13
        "strings"
14

15
        "github.com/UiPath/uipathcli/auth"
16
        "github.com/UiPath/uipathcli/log"
17
        "github.com/UiPath/uipathcli/output"
18
        "github.com/UiPath/uipathcli/utils/converter"
19
        "github.com/UiPath/uipathcli/utils/network"
20
        "github.com/UiPath/uipathcli/utils/stream"
21
        "github.com/UiPath/uipathcli/utils/visualization"
22
)
23

24
const NotConfiguredErrorTemplate = `Run config command to set organization and tenant:
25

26
    uipath config
27

28
For more information you can view the help:
29

30
    uipath config --help
31
`
32

33
// The HttpExecutor implements the Executor interface and constructs HTTP request
34
// from the given command line parameters and configurations.
35
type HttpExecutor struct {
36
        authenticators []auth.Authenticator
37
}
38

39
func (e HttpExecutor) addHeaders(header http.Header, headerParameters []ExecutionParameter) {
1✔
40
        converter := converter.NewStringConverter()
1✔
41
        for _, parameter := range headerParameters {
2✔
42
                headerValue := converter.ToString(parameter.Value)
1✔
43
                header.Set(parameter.Name, headerValue)
1✔
44
        }
1✔
45
}
46

47
func (e HttpExecutor) calculateMultipartSize(parameters []ExecutionParameter) int64 {
1✔
48
        result := int64(0)
1✔
49
        for _, parameter := range parameters {
2✔
50
                switch v := parameter.Value.(type) {
1✔
51
                case string:
×
52
                        result = result + int64(len(v))
×
53
                case stream.Stream:
1✔
54
                        size, err := v.Size()
1✔
55
                        if err == nil {
2✔
56
                                result = result + size
1✔
57
                        }
1✔
58
                }
59
        }
60
        return result
1✔
61
}
62

63
func (e HttpExecutor) writeMultipartForm(writer *multipart.Writer, parameters []ExecutionParameter) error {
1✔
64
        for _, parameter := range parameters {
2✔
65
                switch v := parameter.Value.(type) {
1✔
66
                case string:
×
67
                        w, err := writer.CreateFormField(parameter.Name)
×
68
                        if err != nil {
×
69
                                return fmt.Errorf("Error creating form field '%s': %w", parameter.Name, err)
×
70
                        }
×
71
                        _, err = w.Write([]byte(v))
×
72
                        if err != nil {
×
73
                                return fmt.Errorf("Error writing form field '%s': %w", parameter.Name, err)
×
74
                        }
×
75
                case stream.Stream:
1✔
76
                        w, err := writer.CreateFormFile(parameter.Name, v.Name())
1✔
77
                        if err != nil {
1✔
78
                                return fmt.Errorf("Error writing form file '%s': %w", parameter.Name, err)
×
79
                        }
×
80
                        data, err := v.Data()
1✔
81
                        if err != nil {
2✔
82
                                return err
1✔
83
                        }
1✔
84
                        defer data.Close()
1✔
85
                        _, err = io.Copy(w, data)
1✔
86
                        if err != nil {
1✔
87
                                return fmt.Errorf("Error writing form file '%s': %w", parameter.Name, err)
×
88
                        }
×
89
                }
90
        }
91
        return nil
1✔
92
}
93

94
func (e HttpExecutor) serializeJson(body io.Writer, parameters []ExecutionParameter) error {
1✔
95
        data := map[string]interface{}{}
1✔
96
        for _, parameter := range parameters {
2✔
97
                data[parameter.Name] = parameter.Value
1✔
98
        }
1✔
99
        result, err := json.Marshal(data)
1✔
100
        if err != nil {
1✔
101
                return fmt.Errorf("Error creating body: %w", err)
×
102
        }
×
103
        _, err = body.Write(result)
1✔
104
        if err != nil {
1✔
105
                return fmt.Errorf("Error writing body: %w", err)
×
106
        }
×
107
        return nil
1✔
108
}
109

110
func (e HttpExecutor) validateUri(uri string) (*url.URL, error) {
1✔
111
        if strings.Contains(uri, "{organization}") {
2✔
112
                return nil, fmt.Errorf("Missing organization parameter!\n\n%s", NotConfiguredErrorTemplate)
1✔
113
        }
1✔
114
        if strings.Contains(uri, "{tenant}") {
2✔
115
                return nil, fmt.Errorf("Missing tenant parameter!\n\n%s", NotConfiguredErrorTemplate)
1✔
116
        }
1✔
117

118
        result, err := url.Parse(uri)
1✔
119
        if err != nil {
1✔
120
                return nil, fmt.Errorf("Invalid URI '%s': %w", uri, err)
×
121
        }
×
122
        return result, nil
1✔
123
}
124

125
func (e HttpExecutor) formatUri(baseUri url.URL, route string, pathParameters []ExecutionParameter, queryParameters []ExecutionParameter) (*url.URL, error) {
1✔
126
        uriBuilder := converter.NewUriBuilder(baseUri, route)
1✔
127
        for _, parameter := range pathParameters {
2✔
128
                uriBuilder.FormatPath(parameter.Name, parameter.Value)
1✔
129
        }
1✔
130
        for _, parameter := range queryParameters {
2✔
131
                uriBuilder.AddQueryString(parameter.Name, parameter.Value)
1✔
132
        }
1✔
133
        return e.validateUri(uriBuilder.Build())
1✔
134
}
135

136
func (e HttpExecutor) authenticatorContext(ctx ExecutionContext, url string) auth.AuthenticatorContext {
1✔
137
        authRequest := *auth.NewAuthenticatorRequest(url, map[string]string{})
1✔
138
        return *auth.NewAuthenticatorContext(
1✔
139
                ctx.AuthConfig.Type,
1✔
140
                ctx.AuthConfig.Config,
1✔
141
                ctx.IdentityUri,
1✔
142
                ctx.Settings.OperationId,
1✔
143
                ctx.Settings.Insecure,
1✔
144
                authRequest)
1✔
145
}
1✔
146

147
func (e HttpExecutor) executeAuthenticators(ctx ExecutionContext, url string) (*auth.AuthenticatorResult, error) {
1✔
148
        authContext := e.authenticatorContext(ctx, url)
1✔
149
        for _, authProvider := range e.authenticators {
2✔
150
                result := authProvider.Auth(authContext)
1✔
151
                if result.Error != "" {
2✔
152
                        return nil, errors.New(result.Error)
1✔
153
                }
1✔
154
                authContext.Config = result.Config
1✔
155
                for k, v := range result.RequestHeader {
2✔
156
                        authContext.Request.Header[k] = v
1✔
157
                }
1✔
158
        }
159
        return auth.AuthenticatorSuccess(authContext.Request.Header, authContext.Config), nil
1✔
160
}
161

162
func (e HttpExecutor) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *visualization.ProgressBar) io.Reader {
1✔
163
        if length < 10*1024*1024 {
2✔
164
                return reader
1✔
165
        }
1✔
166
        return visualization.NewProgressReader(reader, func(progress visualization.Progress) {
2✔
167
                displayText := text
1✔
168
                if progress.Completed {
2✔
169
                        displayText = completedText
1✔
170
                }
1✔
171
                progressBar.UpdateProgress(displayText, progress.BytesRead, length, progress.BytesPerSecond)
1✔
172
        })
173
}
174

175
func (e HttpExecutor) writeMultipartBody(bodyWriter *io.PipeWriter, parameters []ExecutionParameter, cancel context.CancelCauseFunc) (string, int64) {
1✔
176
        multipartSize := e.calculateMultipartSize(parameters)
1✔
177
        formWriter := multipart.NewWriter(bodyWriter)
1✔
178
        go func() {
2✔
179
                defer bodyWriter.Close()
1✔
180
                defer formWriter.Close()
1✔
181
                err := e.writeMultipartForm(formWriter, parameters)
1✔
182
                if err != nil {
2✔
183
                        cancel(err)
1✔
184
                        return
1✔
185
                }
1✔
186
        }()
187
        return formWriter.FormDataContentType(), multipartSize
1✔
188
}
189

190
func (e HttpExecutor) writeInputBody(bodyWriter *io.PipeWriter, input stream.Stream, cancel context.CancelCauseFunc) {
1✔
191
        go func() {
2✔
192
                defer bodyWriter.Close()
1✔
193
                data, err := input.Data()
1✔
194
                if err != nil {
1✔
NEW
195
                        cancel(err)
×
196
                        return
×
197
                }
×
198
                defer data.Close()
1✔
199
                _, err = io.Copy(bodyWriter, data)
1✔
200
                if err != nil {
1✔
NEW
201
                        cancel(err)
×
202
                        return
×
203
                }
×
204
        }()
205
}
206

207
func (e HttpExecutor) writeUrlEncodedBody(bodyWriter *io.PipeWriter, parameters []ExecutionParameter, cancel context.CancelCauseFunc) {
1✔
208
        go func() {
2✔
209
                defer bodyWriter.Close()
1✔
210
                queryStringBuilder := converter.NewQueryStringBuilder()
1✔
211
                for _, parameter := range parameters {
2✔
212
                        queryStringBuilder.Add(parameter.Name, parameter.Value)
1✔
213
                }
1✔
214
                queryString := queryStringBuilder.Build()
1✔
215
                _, err := bodyWriter.Write([]byte(queryString))
1✔
216
                if err != nil {
1✔
NEW
217
                        cancel(err)
×
218
                        return
×
219
                }
×
220
        }()
221
}
222

223
func (e HttpExecutor) writeJsonBody(bodyWriter *io.PipeWriter, parameters []ExecutionParameter, cancel context.CancelCauseFunc) {
1✔
224
        go func() {
2✔
225
                defer bodyWriter.Close()
1✔
226
                err := e.serializeJson(bodyWriter, parameters)
1✔
227
                if err != nil {
1✔
NEW
228
                        cancel(err)
×
229
                        return
×
230
                }
×
231
        }()
232
}
233

234
func (e HttpExecutor) writeBody(ctx ExecutionContext, cancel context.CancelCauseFunc) (io.ReadCloser, string, int64, int64) {
1✔
235
        if ctx.Input != nil {
2✔
236
                reader, writer := io.Pipe()
1✔
237
                e.writeInputBody(writer, ctx.Input, cancel)
1✔
238
                contentLength, _ := ctx.Input.Size()
1✔
239
                return reader, ctx.ContentType, contentLength, contentLength
1✔
240
        }
1✔
241
        formParameters := ctx.Parameters.Form()
1✔
242
        if len(formParameters) > 0 {
2✔
243
                reader, writer := io.Pipe()
1✔
244
                contentType, multipartSize := e.writeMultipartBody(writer, formParameters, cancel)
1✔
245
                return reader, contentType, -1, multipartSize
1✔
246
        }
1✔
247
        bodyParameters := ctx.Parameters.Body()
1✔
248
        if len(bodyParameters) > 0 && ctx.ContentType == "application/x-www-form-urlencoded" {
2✔
249
                reader, writer := io.Pipe()
1✔
250
                e.writeUrlEncodedBody(writer, bodyParameters, cancel)
1✔
251
                return reader, ctx.ContentType, -1, -1
1✔
252
        }
1✔
253
        if len(bodyParameters) > 0 {
2✔
254
                reader, writer := io.Pipe()
1✔
255
                e.writeJsonBody(writer, bodyParameters, cancel)
1✔
256
                return reader, ctx.ContentType, -1, -1
1✔
257
        }
1✔
258
        return io.NopCloser(bytes.NewReader([]byte{})), ctx.ContentType, -1, -1
1✔
259
}
260

261
func (e HttpExecutor) pathParameters(ctx ExecutionContext) []ExecutionParameter {
1✔
262
        pathParameters := ctx.Parameters.Path()
1✔
263
        if ctx.Organization != "" {
2✔
264
                pathParameters = append(pathParameters, *NewExecutionParameter("organization", ctx.Organization, "path"))
1✔
265
        }
1✔
266
        if ctx.Tenant != "" {
2✔
267
                pathParameters = append(pathParameters, *NewExecutionParameter("tenant", ctx.Tenant, "path"))
1✔
268
        }
1✔
269
        return pathParameters
1✔
270
}
271

272
func (e HttpExecutor) httpClientSettings(ctx ExecutionContext) network.HttpClientSettings {
1✔
273
        return *network.NewHttpClientSettings(
1✔
274
                ctx.Debug,
1✔
275
                ctx.Settings.OperationId,
1✔
276
                ctx.Settings.Timeout,
1✔
277
                ctx.Settings.MaxAttempts,
1✔
278
                ctx.Settings.Insecure)
1✔
279
}
1✔
280

281
func (e HttpExecutor) Call(ctx ExecutionContext, writer output.OutputWriter, logger log.Logger) error {
1✔
282
        uri, err := e.formatUri(ctx.BaseUri, ctx.Route, e.pathParameters(ctx), ctx.Parameters.Query())
1✔
283
        if err != nil {
2✔
284
                return err
1✔
285
        }
1✔
286
        context, cancel := context.WithCancelCause(context.Background())
1✔
287
        bodyReader, contentType, contentLength, size := e.writeBody(ctx, cancel)
1✔
288
        uploadBar := visualization.NewProgressBar(logger)
1✔
289
        uploadReader := e.progressReader("uploading...", "completing  ", bodyReader, size, uploadBar)
1✔
290
        defer uploadBar.Remove()
1✔
291

1✔
292
        auth, err := e.executeAuthenticators(ctx, uri.String())
1✔
293
        if err != nil {
2✔
294
                return err
1✔
295
        }
1✔
296

297
        header := http.Header{}
1✔
298
        if contentType != "" {
2✔
299
                header.Set("Content-Type", contentType)
1✔
300
        }
1✔
301
        e.addHeaders(header, ctx.Parameters.Header())
1✔
302
        for k, v := range auth.RequestHeader {
2✔
303
                header.Set(k, v)
1✔
304
        }
1✔
305
        request := network.NewHttpRequest(ctx.Method, uri.String(), header, uploadReader, contentLength)
1✔
306

1✔
307
        client := network.NewHttpClient(logger, e.httpClientSettings(ctx))
1✔
308
        response, err := client.SendWithContext(request, context)
1✔
309
        if err != nil {
2✔
310
                return err
1✔
311
        }
1✔
312
        defer response.Body.Close()
1✔
313
        downloadBar := visualization.NewProgressBar(logger)
1✔
314
        downloadReader := e.progressReader("downloading...", "completing    ", response.Body, response.ContentLength, downloadBar)
1✔
315
        defer downloadBar.Remove()
1✔
316
        body, err := io.ReadAll(downloadReader)
1✔
317
        if err != nil {
1✔
NEW
318
                return fmt.Errorf("Error reading response body: %w", err)
×
319
        }
×
320
        err = writer.WriteResponse(*output.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body)))
1✔
321
        if err != nil {
2✔
322
                return err
1✔
323
        }
1✔
324
        return nil
1✔
325
}
326

327
func NewHttpExecutor(authenticators []auth.Authenticator) *HttpExecutor {
1✔
328
        return &HttpExecutor{authenticators}
1✔
329
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc