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

UiPath / uipathcli / 12410563606

19 Dec 2024 10:10AM UTC coverage: 89.869% (+0.1%) from 89.735%
12410563606

push

github

thschmitt
Simplify parameter handling in plugins

71 of 75 new or added lines in 5 files covered. (94.67%)

37 existing lines in 5 files now uncovered.

4879 of 5429 relevant lines covered (89.87%)

1.01 hits per line

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

86.25
/plugin/digitizer/digitize_command.go
1
package digitzer
2

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

17
        "github.com/UiPath/uipathcli/log"
18
        "github.com/UiPath/uipathcli/output"
19
        "github.com/UiPath/uipathcli/plugin"
20
        "github.com/UiPath/uipathcli/utils"
21
)
22

23
// The DigitizeCommand is a convenient wrapper over the async digitizer API
24
// to make it seem like it is a single sync call.
25
type DigitizeCommand struct{}
26

27
func (c DigitizeCommand) Command() plugin.Command {
1✔
28
        return *plugin.NewCommand("du").
1✔
29
                WithCategory("digitization", "Document Digitization", "Digitizes a document, extracting its Document Object Model (DOM) and text.").
1✔
30
                WithOperation("digitize", "Digitize file", "Digitize the given file").
1✔
31
                WithParameter("project-id", plugin.ParameterTypeString, "The project id", false).
1✔
32
                WithParameter("file", plugin.ParameterTypeBinary, "The file to digitize", true).
1✔
33
                WithParameter("content-type", plugin.ParameterTypeString, "The content type", false)
1✔
34
}
1✔
35

36
func (c DigitizeCommand) Execute(context plugin.ExecutionContext, writer output.OutputWriter, logger log.Logger) error {
1✔
37
        if context.Organization == "" {
2✔
38
                return errors.New("Organization is not set")
1✔
39
        }
1✔
40
        if context.Tenant == "" {
2✔
41
                return errors.New("Tenant is not set")
1✔
42
        }
1✔
43
        documentId, err := c.startDigitization(context, logger)
1✔
44
        if err != nil {
2✔
45
                return err
1✔
46
        }
1✔
47

48
        for i := 1; i <= 60; i++ {
2✔
49
                finished, err := c.waitForDigitization(documentId, context, writer, logger)
1✔
50
                if err != nil {
2✔
51
                        return err
1✔
52
                }
1✔
53
                if finished {
2✔
54
                        return nil
1✔
55
                }
1✔
56
                time.Sleep(1 * time.Second)
×
57
        }
58
        return fmt.Errorf("Digitization with documentId '%s' did not finish in time", documentId)
×
59
}
60

61
func (c DigitizeCommand) startDigitization(context plugin.ExecutionContext, logger log.Logger) (string, error) {
1✔
62
        uploadBar := utils.NewProgressBar(logger)
1✔
63
        defer uploadBar.Remove()
1✔
64
        requestError := make(chan error)
1✔
65
        request, err := c.createDigitizeRequest(context, uploadBar, requestError)
1✔
66
        if err != nil {
1✔
67
                return "", err
×
68
        }
×
69
        if context.Debug {
2✔
70
                c.logRequest(logger, request)
1✔
71
        }
1✔
72
        response, err := c.send(request, context.Insecure, requestError)
1✔
73
        if err != nil {
2✔
74
                return "", fmt.Errorf("Error sending request: %w", err)
1✔
75
        }
1✔
76
        defer response.Body.Close()
1✔
77
        body, err := io.ReadAll(response.Body)
1✔
78
        if err != nil {
1✔
79
                return "", fmt.Errorf("Error reading response: %w", err)
×
80
        }
×
81
        c.logResponse(logger, response, body)
1✔
82
        if response.StatusCode != http.StatusAccepted {
2✔
83
                return "", fmt.Errorf("Digitizer returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
84
        }
1✔
85
        var result digitizeResponse
1✔
86
        err = json.Unmarshal(body, &result)
1✔
87
        if err != nil {
1✔
88
                return "", fmt.Errorf("Error parsing json response: %w", err)
×
89
        }
×
90
        return result.DocumentId, nil
1✔
91
}
92

93
func (c DigitizeCommand) waitForDigitization(documentId string, context plugin.ExecutionContext, writer output.OutputWriter, logger log.Logger) (bool, error) {
1✔
94
        request, err := c.createDigitizeStatusRequest(documentId, context)
1✔
95
        if err != nil {
1✔
96
                return true, err
×
97
        }
×
98
        if context.Debug {
2✔
99
                c.logRequest(logger, request)
1✔
100
        }
1✔
101
        response, err := c.sendRequest(request, context.Insecure)
1✔
102
        if err != nil {
1✔
103
                return true, fmt.Errorf("Error sending request: %w", err)
×
104
        }
×
105
        defer response.Body.Close()
1✔
106
        body, err := io.ReadAll(response.Body)
1✔
107
        if err != nil {
1✔
108
                return true, fmt.Errorf("Error reading response: %w", err)
×
109
        }
×
110
        c.logResponse(logger, response, body)
1✔
111
        if response.StatusCode != http.StatusOK {
2✔
112
                return true, fmt.Errorf("Digitizer returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
113
        }
1✔
114
        var result digitizeResultResponse
1✔
115
        err = json.Unmarshal(body, &result)
1✔
116
        if err != nil {
1✔
117
                return true, fmt.Errorf("Error parsing json response: %w", err)
×
118
        }
×
119
        if result.Status == "NotStarted" || result.Status == "Running" {
1✔
120
                return false, nil
×
121
        }
×
122
        err = writer.WriteResponse(*output.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body)))
1✔
123
        return true, err
1✔
124
}
125

126
func (c DigitizeCommand) createDigitizeRequest(context plugin.ExecutionContext, uploadBar *utils.ProgressBar, requestError chan error) (*http.Request, error) {
1✔
127
        projectId := c.getProjectId(context.Parameters)
1✔
128

1✔
129
        var err error
1✔
130
        file := context.Input
1✔
131
        if file == nil {
2✔
132
                file = c.getFileParameter(context.Parameters)
1✔
133
        }
1✔
134
        contentType := c.getParameter("content-type", context.Parameters)
1✔
135
        if contentType == "" {
2✔
136
                contentType = "application/octet-stream"
1✔
137
        }
1✔
138

139
        bodyReader, bodyWriter := io.Pipe()
1✔
140
        contentType, contentLength := c.writeMultipartBody(bodyWriter, file, contentType, requestError)
1✔
141
        uploadReader := c.progressReader("uploading...", "completing  ", bodyReader, contentLength, uploadBar)
1✔
142

1✔
143
        uri := c.formatUri(context.BaseUri, context.Organization, context.Tenant, projectId) + "/digitization/start?api-version=1"
1✔
144
        request, err := http.NewRequest("POST", uri, uploadReader)
1✔
145
        if err != nil {
1✔
UNCOV
146
                return nil, err
×
147
        }
×
148
        request.Header.Add("Content-Type", contentType)
1✔
149
        for key, value := range context.Auth.Header {
1✔
UNCOV
150
                request.Header.Add(key, value)
×
UNCOV
151
        }
×
152
        return request, nil
1✔
153
}
154

155
func (c DigitizeCommand) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *utils.ProgressBar) io.Reader {
1✔
156
        if length < 10*1024*1024 {
2✔
157
                return reader
1✔
158
        }
1✔
159
        progressReader := utils.NewProgressReader(reader, func(progress utils.Progress) {
2✔
160
                displayText := text
1✔
161
                if progress.Completed {
2✔
162
                        displayText = completedText
1✔
163
                }
1✔
164
                progressBar.Update(displayText, progress.BytesRead, length, progress.BytesPerSecond)
1✔
165
        })
166
        return progressReader
1✔
167
}
168

169
func (c DigitizeCommand) formatUri(baseUri url.URL, org string, tenant string, projectId string) string {
1✔
170
        path := baseUri.Path
1✔
171
        if baseUri.Path == "" {
2✔
172
                path = "/{organization}/{tenant}/du_/api/framework/projects/{projectId}"
1✔
173
        }
1✔
174
        path = strings.ReplaceAll(path, "{organization}", org)
1✔
175
        path = strings.ReplaceAll(path, "{tenant}", tenant)
1✔
176
        path = strings.ReplaceAll(path, "{projectId}", projectId)
1✔
177
        path = strings.TrimSuffix(path, "/")
1✔
178
        return fmt.Sprintf("%s://%s%s", baseUri.Scheme, baseUri.Host, path)
1✔
179
}
180

181
func (c DigitizeCommand) createDigitizeStatusRequest(documentId string, context plugin.ExecutionContext) (*http.Request, error) {
1✔
182
        projectId := c.getProjectId(context.Parameters)
1✔
183
        uri := c.formatUri(context.BaseUri, context.Organization, context.Tenant, projectId) + fmt.Sprintf("/digitization/result/%s?api-version=1", documentId)
1✔
184
        request, err := http.NewRequest("GET", uri, &bytes.Buffer{})
1✔
185
        if err != nil {
1✔
186
                return nil, err
×
187
        }
×
188
        for key, value := range context.Auth.Header {
1✔
UNCOV
189
                request.Header.Add(key, value)
×
UNCOV
190
        }
×
191
        return request, nil
1✔
192
}
193

194
func (c DigitizeCommand) calculateMultipartSize(stream utils.Stream) int64 {
1✔
195
        size, _ := stream.Size()
1✔
196
        return size
1✔
197
}
1✔
198

199
func (c DigitizeCommand) writeMultipartForm(writer *multipart.Writer, stream utils.Stream, contentType string) error {
1✔
200
        filePart := textproto.MIMEHeader{}
1✔
201
        filePart.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, stream.Name()))
1✔
202
        filePart.Set("Content-Type", contentType)
1✔
203
        w, err := writer.CreatePart(filePart)
1✔
204
        if err != nil {
1✔
UNCOV
205
                return fmt.Errorf("Error creating form field 'file': %w", err)
×
UNCOV
206
        }
×
207
        data, err := stream.Data()
1✔
208
        if err != nil {
2✔
209
                return err
1✔
210
        }
1✔
211
        defer data.Close()
1✔
212
        _, err = io.Copy(w, data)
1✔
213
        if err != nil {
1✔
UNCOV
214
                return fmt.Errorf("Error writing form field 'file': %w", err)
×
UNCOV
215
        }
×
216
        return nil
1✔
217
}
218

219
func (c DigitizeCommand) writeMultipartBody(bodyWriter *io.PipeWriter, stream utils.Stream, contentType string, errorChan chan error) (string, int64) {
1✔
220
        contentLength := c.calculateMultipartSize(stream)
1✔
221
        formWriter := multipart.NewWriter(bodyWriter)
1✔
222
        go func() {
2✔
223
                defer bodyWriter.Close()
1✔
224
                defer formWriter.Close()
1✔
225
                err := c.writeMultipartForm(formWriter, stream, contentType)
1✔
226
                if err != nil {
2✔
227
                        errorChan <- err
1✔
228
                        return
1✔
229
                }
1✔
230
        }()
231
        return formWriter.FormDataContentType(), contentLength
1✔
232
}
233

234
func (c DigitizeCommand) send(request *http.Request, insecure bool, errorChan chan error) (*http.Response, error) {
1✔
235
        responseChan := make(chan *http.Response)
1✔
236
        go func(request *http.Request) {
2✔
237
                response, err := c.sendRequest(request, insecure)
1✔
238
                if err != nil {
1✔
UNCOV
239
                        errorChan <- err
×
UNCOV
240
                        return
×
UNCOV
241
                }
×
242
                responseChan <- response
1✔
243
        }(request)
244

245
        select {
1✔
246
        case err := <-errorChan:
1✔
247
                return nil, err
1✔
248
        case response := <-responseChan:
1✔
249
                return response, nil
1✔
250
        }
251
}
252

253
func (c DigitizeCommand) sendRequest(request *http.Request, insecure bool) (*http.Response, error) {
1✔
254
        transport := &http.Transport{
1✔
255
                TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, //nolint // This is user configurable and disabled by default
1✔
256
        }
1✔
257
        client := &http.Client{Transport: transport}
1✔
258
        return client.Do(request)
1✔
259
}
1✔
260

261
func (c DigitizeCommand) getProjectId(parameters []plugin.ExecutionParameter) string {
1✔
262
        projectId := c.getParameter("project-id", parameters)
1✔
263
        if projectId == "" {
2✔
264
                projectId = "00000000-0000-0000-0000-000000000000"
1✔
265
        }
1✔
266
        return projectId
1✔
267
}
268

269
func (c DigitizeCommand) getParameter(name string, parameters []plugin.ExecutionParameter) string {
1✔
270
        result := ""
1✔
271
        for _, p := range parameters {
2✔
272
                if p.Name == name {
2✔
273
                        if data, ok := p.Value.(string); ok {
2✔
274
                                result = data
1✔
275
                                break
1✔
276
                        }
277
                }
278
        }
279
        return result
1✔
280
}
281

282
func (c DigitizeCommand) getFileParameter(parameters []plugin.ExecutionParameter) utils.Stream {
1✔
283
        var result utils.Stream
1✔
284
        for _, p := range parameters {
2✔
285
                if p.Name == "file" {
2✔
286
                        if stream, ok := p.Value.(utils.Stream); ok {
2✔
287
                                result = stream
1✔
288
                                break
1✔
289
                        }
290
                }
291
        }
292
        return result
1✔
293
}
294

295
func (c DigitizeCommand) logRequest(logger log.Logger, request *http.Request) {
1✔
296
        buffer := &bytes.Buffer{}
1✔
297
        _, _ = buffer.ReadFrom(request.Body)
1✔
298
        body := buffer.Bytes()
1✔
299
        request.Body = io.NopCloser(bytes.NewReader(body))
1✔
300
        requestInfo := log.NewRequestInfo(request.Method, request.URL.String(), request.Proto, request.Header, bytes.NewReader(body))
1✔
301
        logger.LogRequest(*requestInfo)
1✔
302
}
1✔
303

304
func (c DigitizeCommand) logResponse(logger log.Logger, response *http.Response, body []byte) {
1✔
305
        responseInfo := log.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body))
1✔
306
        logger.LogResponse(*responseInfo)
1✔
307
}
1✔
308

309
func NewDigitizeCommand() *DigitizeCommand {
1✔
310
        return &DigitizeCommand{}
1✔
311
}
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