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

uber / cadence / 01884958-2b82-43c7-9de5-c04744820754

23 May 2023 04:20PM UTC coverage: 57.301% (+0.007%) from 57.294%
01884958-2b82-43c7-9de5-c04744820754

push

buildkite

GitHub
Parse JWT flags when creating a context (#5296)

10 of 10 new or added lines in 1 file covered. (100.0%)

87052 of 151921 relevant lines covered (57.3%)

2499.91 hits per line

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

52.66
/tools/cli/utils.go
1
// Copyright (c) 2017-2020 Uber Technologies Inc.
2
// Portions of the Software are attributed to Copyright (c) 2020 Temporal Technologies Inc.
3
//
4
// Permission is hereby granted, free of charge, to any person obtaining a copy
5
// of this software and associated documentation files (the "Software"), to deal
6
// in the Software without restriction, including without limitation the rights
7
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
// copies of the Software, and to permit persons to whom the Software is
9
// furnished to do so, subject to the following conditions:
10
//
11
// The above copyright notice and this permission notice shall be included in
12
// all copies or substantial portions of the Software.
13
//
14
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
// THE SOFTWARE.
21

22
package cli
23

24
import (
25
        "bufio"
26
        "bytes"
27
        "context"
28
        "encoding/json"
29
        "errors"
30
        "fmt"
31
        "io"
32
        "io/ioutil"
33
        "os"
34
        "reflect"
35
        "regexp"
36
        "runtime/debug"
37
        "sort"
38
        "strconv"
39
        "strings"
40
        "time"
41

42
        "github.com/cristalhq/jwt/v3"
43
        "github.com/fatih/color"
44
        "github.com/urfave/cli"
45
        "github.com/valyala/fastjson"
46

47
        "github.com/uber/cadence/client/frontend"
48
        "github.com/uber/cadence/common"
49
        "github.com/uber/cadence/common/authorization"
50
        "github.com/uber/cadence/common/pagination"
51
        "github.com/uber/cadence/common/types"
52
)
53

54
// JSONHistorySerializer is used to encode history event in JSON
55
type JSONHistorySerializer struct{}
56

57
// Serialize serializes history.
58
func (j *JSONHistorySerializer) Serialize(h *types.History) ([]byte, error) {
×
59
        return json.Marshal(h.Events)
×
60
}
×
61

62
// Deserialize deserializes history
63
func (j *JSONHistorySerializer) Deserialize(data []byte) (*types.History, error) {
×
64
        var events []*types.HistoryEvent
×
65
        err := json.Unmarshal(data, &events)
×
66
        if err != nil {
×
67
                return nil, err
×
68
        }
×
69
        return &types.History{Events: events}, nil
×
70
}
71

72
// GetHistory helper method to iterate over all pages and return complete list of history events
73
func GetHistory(ctx context.Context, workflowClient frontend.Client, domain, workflowID, runID string) (*types.History, error) {
4✔
74
        events := []*types.HistoryEvent{}
4✔
75
        iterator, err := GetWorkflowHistoryIterator(ctx, workflowClient, domain, workflowID, runID, false, types.HistoryEventFilterTypeAllEvent.Ptr())
4✔
76
        for iterator.HasNext() {
8✔
77
                entity, err := iterator.Next()
4✔
78
                if err != nil {
4✔
79
                        return nil, err
×
80
                }
×
81
                events = append(events, entity.(*types.HistoryEvent))
4✔
82
        }
83
        history := &types.History{}
4✔
84
        history.Events = events
4✔
85
        return history, err
4✔
86
}
87

88
// GetWorkflowHistoryIterator returns a HistoryEvent iterator
89
func GetWorkflowHistoryIterator(
90
        ctx context.Context,
91
        workflowClient frontend.Client,
92
        domain,
93
        workflowID,
94
        runID string,
95
        isLongPoll bool,
96
        filterType *types.HistoryEventFilterType,
97
) (pagination.Iterator, error) {
11✔
98
        paginate := func(ctx context.Context, pageToken pagination.PageToken) (pagination.Page, error) {
22✔
99
                tcCtx, cancel := context.WithTimeout(ctx, 25*time.Second)
11✔
100
                defer cancel()
11✔
101

11✔
102
                var nextPageToken []byte
11✔
103
                if pageToken != nil {
11✔
104
                        nextPageToken, _ = pageToken.([]byte)
×
105
                }
×
106
                request := &types.GetWorkflowExecutionHistoryRequest{
11✔
107
                        Domain: domain,
11✔
108
                        Execution: &types.WorkflowExecution{
11✔
109
                                WorkflowID: workflowID,
11✔
110
                                RunID:      runID,
11✔
111
                        },
11✔
112
                        WaitForNewEvent:        isLongPoll,
11✔
113
                        HistoryEventFilterType: filterType,
11✔
114
                        NextPageToken:          nextPageToken,
11✔
115
                        SkipArchival:           isLongPoll,
11✔
116
                }
11✔
117

11✔
118
                var resp *types.GetWorkflowExecutionHistoryResponse
11✔
119
                var err error
11✔
120
        Loop:
11✔
121
                for {
22✔
122
                        resp, err = workflowClient.GetWorkflowExecutionHistory(tcCtx, request)
11✔
123
                        if err != nil {
11✔
124
                                return pagination.Page{}, err
×
125
                        }
×
126

127
                        if isLongPoll && len(resp.History.Events) == 0 && len(resp.NextPageToken) != 0 {
11✔
128
                                request.NextPageToken = resp.NextPageToken
×
129
                                continue Loop
×
130
                        }
131
                        break Loop
11✔
132
                }
133
                entities := make([]pagination.Entity, len(resp.History.Events))
11✔
134
                for i, e := range resp.History.Events {
22✔
135
                        entities[i] = e
11✔
136
                }
11✔
137
                var nextToken interface{} = resp.NextPageToken
11✔
138
                if len(resp.NextPageToken) == 0 {
22✔
139
                        nextToken = nil
11✔
140
                }
11✔
141
                page := pagination.Page{
11✔
142
                        CurrentToken: pageToken,
11✔
143
                        NextToken:    nextToken,
11✔
144
                        Entities:     entities,
11✔
145
                }
11✔
146
                return page, err
11✔
147
        }
148
        return pagination.NewIterator(ctx, nil, paginate), nil
11✔
149
}
150

151
// HistoryEventToString convert HistoryEvent to string
152
func HistoryEventToString(e *types.HistoryEvent, printFully bool, maxFieldLength int) string {
6✔
153
        data := getEventAttributes(e)
6✔
154
        return anyToString(data, printFully, maxFieldLength)
6✔
155
}
6✔
156

157
func anyToString(d interface{}, printFully bool, maxFieldLength int) string {
39✔
158
        // fields related to schedule are of time.Time type, and we shouldn't dive
39✔
159
        // into it with reflection - it's fields are private.
39✔
160
        tm, ok := d.(time.Time)
39✔
161
        if ok {
42✔
162
                return trimText(tm.String(), maxFieldLength)
3✔
163
        }
3✔
164

165
        v := reflect.ValueOf(d)
36✔
166
        switch v.Kind() {
36✔
167
        case reflect.Ptr:
9✔
168
                return anyToString(v.Elem().Interface(), printFully, maxFieldLength)
9✔
169
        case reflect.Struct:
27✔
170
                var buf bytes.Buffer
27✔
171
                t := reflect.TypeOf(d)
27✔
172
                buf.WriteString("{")
27✔
173
                for i := 0; i < v.NumField(); i++ {
339✔
174
                        f := v.Field(i)
312✔
175
                        if f.Kind() == reflect.Invalid {
312✔
176
                                continue
×
177
                        }
178
                        fieldValue := valueToString(f, printFully, maxFieldLength)
312✔
179
                        if len(fieldValue) == 0 {
522✔
180
                                continue
210✔
181
                        }
182
                        if buf.Len() > 1 {
177✔
183
                                buf.WriteString(", ")
75✔
184
                        }
75✔
185
                        fieldName := t.Field(i).Name
102✔
186
                        if !isAttributeName(fieldName) {
203✔
187
                                if !printFully {
168✔
188
                                        fieldValue = trimTextAndBreakWords(fieldValue, maxFieldLength)
67✔
189
                                } else if maxFieldLength != 0 { // for command run workflow and observe history
101✔
190
                                        fieldValue = trimText(fieldValue, maxFieldLength)
×
191
                                }
×
192
                        }
193
                        if fieldName == "Reason" || fieldName == "Details" || fieldName == "Cause" {
102✔
194
                                buf.WriteString(fmt.Sprintf("%s:%s", color.RedString(fieldName), color.MagentaString(fieldValue)))
×
195
                        } else {
102✔
196
                                buf.WriteString(fmt.Sprintf("%s:%s", fieldName, fieldValue))
102✔
197
                        }
102✔
198
                }
199
                buf.WriteString("}")
27✔
200
                return buf.String()
27✔
201
        default:
×
202
                return fmt.Sprint(d)
×
203
        }
204
}
205

206
func valueToString(v reflect.Value, printFully bool, maxFieldLength int) string {
524✔
207
        switch v.Kind() {
524✔
208
        case reflect.Ptr:
212✔
209
                return valueToString(v.Elem(), printFully, maxFieldLength)
212✔
210
        case reflect.Struct:
18✔
211
                return anyToString(v.Interface(), printFully, maxFieldLength)
18✔
212
        case reflect.Invalid:
180✔
213
                return ""
180✔
214
        case reflect.Slice:
21✔
215
                if v.Type().Elem().Kind() == reflect.Uint8 {
42✔
216
                        n := string(v.Bytes())
21✔
217
                        if n != "" && n[len(n)-1] == '\n' {
21✔
218
                                return fmt.Sprintf("[%v]", n[:len(n)-1])
×
219
                        }
×
220
                        return fmt.Sprintf("[%v]", n)
21✔
221
                }
222
                return fmt.Sprintf("[len=%d]", v.Len())
×
223
        case reflect.Map:
11✔
224
                str := "map{"
11✔
225
                for i, key := range v.MapKeys() {
14✔
226
                        str += key.String() + ":"
3✔
227
                        val := v.MapIndex(key)
3✔
228
                        switch val.Interface().(type) {
3✔
229
                        case []byte:
3✔
230
                                str += string(val.Interface().([]byte))
3✔
231
                        default:
×
232
                                str += val.String()
×
233
                        }
234
                        if i != len(v.MapKeys())-1 {
4✔
235
                                str += ", "
1✔
236
                        }
1✔
237
                }
238
                str += "}"
11✔
239
                return str
11✔
240
        default:
82✔
241
                return fmt.Sprint(v.Interface())
82✔
242
        }
243
}
244

245
// limit the maximum length for each field
246
func trimText(input string, maxFieldLength int) string {
70✔
247
        if len(input) > maxFieldLength {
72✔
248
                input = fmt.Sprintf("%s ... %s", input[:maxFieldLength/2], input[(len(input)-maxFieldLength/2):])
2✔
249
        }
2✔
250
        return input
70✔
251
}
252

253
// limit the maximum length for each field, and break long words for table item correctly wrap words
254
func trimTextAndBreakWords(input string, maxFieldLength int) string {
67✔
255
        input = trimText(input, maxFieldLength)
67✔
256
        return breakLongWords(input, maxWordLength)
67✔
257
}
67✔
258

259
// long words will make output in table cell looks bad,
260
// break long text "ltltltltllt..." to "ltlt ltlt lt..." will make use of table autowrap so that output is pretty.
261
func breakLongWords(input string, maxWordLength int) string {
73✔
262
        if len(input) <= maxWordLength {
141✔
263
                return input
68✔
264
        }
68✔
265

266
        cnt := 0
5✔
267
        for i := 0; i < len(input); i++ {
553✔
268
                if cnt == maxWordLength {
558✔
269
                        cnt = 0
10✔
270
                        input = input[:i] + " " + input[i:]
10✔
271
                        continue
10✔
272
                }
273
                cnt++
538✔
274
                if input[i] == ' ' {
543✔
275
                        cnt = 0
5✔
276
                }
5✔
277
        }
278
        return input
5✔
279
}
280

281
// ColorEvent takes an event and return string with color
282
// Event with color mapping rules:
283
//
284
//        Failed - red
285
//        Timeout - yellow
286
//        Canceled - magenta
287
//        Completed - green
288
//        Started - blue
289
//        Others - default (white/black)
290
func ColorEvent(e *types.HistoryEvent) string {
11✔
291
        var data string
11✔
292
        switch e.GetEventType() {
11✔
293
        case types.EventTypeWorkflowExecutionStarted:
11✔
294
                data = color.BlueString(e.EventType.String())
11✔
295

296
        case types.EventTypeWorkflowExecutionCompleted:
×
297
                data = color.GreenString(e.EventType.String())
×
298

299
        case types.EventTypeWorkflowExecutionFailed:
×
300
                data = color.RedString(e.EventType.String())
×
301

302
        case types.EventTypeWorkflowExecutionTimedOut:
×
303
                data = color.YellowString(e.EventType.String())
×
304

305
        case types.EventTypeDecisionTaskScheduled:
×
306
                data = e.EventType.String()
×
307

308
        case types.EventTypeDecisionTaskStarted:
×
309
                data = e.EventType.String()
×
310

311
        case types.EventTypeDecisionTaskCompleted:
×
312
                data = e.EventType.String()
×
313

314
        case types.EventTypeDecisionTaskTimedOut:
×
315
                data = color.YellowString(e.EventType.String())
×
316

317
        case types.EventTypeActivityTaskScheduled:
×
318
                data = e.EventType.String()
×
319

320
        case types.EventTypeActivityTaskStarted:
×
321
                data = e.EventType.String()
×
322

323
        case types.EventTypeActivityTaskCompleted:
×
324
                data = e.EventType.String()
×
325

326
        case types.EventTypeActivityTaskFailed:
×
327
                data = color.RedString(e.EventType.String())
×
328

329
        case types.EventTypeActivityTaskTimedOut:
×
330
                data = color.YellowString(e.EventType.String())
×
331

332
        case types.EventTypeActivityTaskCancelRequested:
×
333
                data = e.EventType.String()
×
334

335
        case types.EventTypeRequestCancelActivityTaskFailed:
×
336
                data = color.RedString(e.EventType.String())
×
337

338
        case types.EventTypeActivityTaskCanceled:
×
339
                data = e.EventType.String()
×
340

341
        case types.EventTypeTimerStarted:
×
342
                data = e.EventType.String()
×
343

344
        case types.EventTypeTimerFired:
×
345
                data = e.EventType.String()
×
346

347
        case types.EventTypeCancelTimerFailed:
×
348
                data = color.RedString(e.EventType.String())
×
349

350
        case types.EventTypeTimerCanceled:
×
351
                data = color.MagentaString(e.EventType.String())
×
352

353
        case types.EventTypeWorkflowExecutionCancelRequested:
×
354
                data = e.EventType.String()
×
355

356
        case types.EventTypeWorkflowExecutionCanceled:
×
357
                data = color.MagentaString(e.EventType.String())
×
358

359
        case types.EventTypeRequestCancelExternalWorkflowExecutionInitiated:
×
360
                data = e.EventType.String()
×
361

362
        case types.EventTypeRequestCancelExternalWorkflowExecutionFailed:
×
363
                data = color.RedString(e.EventType.String())
×
364

365
        case types.EventTypeExternalWorkflowExecutionCancelRequested:
×
366
                data = e.EventType.String()
×
367

368
        case types.EventTypeMarkerRecorded:
×
369
                data = e.EventType.String()
×
370

371
        case types.EventTypeWorkflowExecutionSignaled:
×
372
                data = e.EventType.String()
×
373

374
        case types.EventTypeWorkflowExecutionTerminated:
×
375
                data = e.EventType.String()
×
376

377
        case types.EventTypeWorkflowExecutionContinuedAsNew:
×
378
                data = e.EventType.String()
×
379

380
        case types.EventTypeStartChildWorkflowExecutionInitiated:
×
381
                data = e.EventType.String()
×
382

383
        case types.EventTypeStartChildWorkflowExecutionFailed:
×
384
                data = color.RedString(e.EventType.String())
×
385

386
        case types.EventTypeChildWorkflowExecutionStarted:
×
387
                data = color.BlueString(e.EventType.String())
×
388

389
        case types.EventTypeChildWorkflowExecutionCompleted:
×
390
                data = color.GreenString(e.EventType.String())
×
391

392
        case types.EventTypeChildWorkflowExecutionFailed:
×
393
                data = color.RedString(e.EventType.String())
×
394

395
        case types.EventTypeChildWorkflowExecutionCanceled:
×
396
                data = color.MagentaString(e.EventType.String())
×
397

398
        case types.EventTypeChildWorkflowExecutionTimedOut:
×
399
                data = color.YellowString(e.EventType.String())
×
400

401
        case types.EventTypeChildWorkflowExecutionTerminated:
×
402
                data = e.EventType.String()
×
403

404
        case types.EventTypeSignalExternalWorkflowExecutionInitiated:
×
405
                data = e.EventType.String()
×
406

407
        case types.EventTypeSignalExternalWorkflowExecutionFailed:
×
408
                data = color.RedString(e.EventType.String())
×
409

410
        case types.EventTypeExternalWorkflowExecutionSignaled:
×
411
                data = e.EventType.String()
×
412

413
        case types.EventTypeUpsertWorkflowSearchAttributes:
×
414
                data = e.EventType.String()
×
415

416
        default:
×
417
                data = e.EventType.String()
×
418
        }
419
        return data
11✔
420
}
421

422
func getEventAttributes(e *types.HistoryEvent) interface{} {
6✔
423
        var data interface{}
6✔
424
        switch e.GetEventType() {
6✔
425
        case types.EventTypeWorkflowExecutionStarted:
6✔
426
                data = e.WorkflowExecutionStartedEventAttributes
6✔
427

428
        case types.EventTypeWorkflowExecutionCompleted:
×
429
                data = e.WorkflowExecutionCompletedEventAttributes
×
430

431
        case types.EventTypeWorkflowExecutionFailed:
×
432
                data = e.WorkflowExecutionFailedEventAttributes
×
433

434
        case types.EventTypeWorkflowExecutionTimedOut:
×
435
                data = e.WorkflowExecutionTimedOutEventAttributes
×
436

437
        case types.EventTypeDecisionTaskScheduled:
×
438
                data = e.DecisionTaskScheduledEventAttributes
×
439

440
        case types.EventTypeDecisionTaskStarted:
×
441
                data = e.DecisionTaskStartedEventAttributes
×
442

443
        case types.EventTypeDecisionTaskCompleted:
×
444
                data = e.DecisionTaskCompletedEventAttributes
×
445

446
        case types.EventTypeDecisionTaskTimedOut:
×
447
                data = e.DecisionTaskTimedOutEventAttributes
×
448

449
        case types.EventTypeActivityTaskScheduled:
×
450
                data = e.ActivityTaskScheduledEventAttributes
×
451

452
        case types.EventTypeActivityTaskStarted:
×
453
                data = e.ActivityTaskStartedEventAttributes
×
454

455
        case types.EventTypeActivityTaskCompleted:
×
456
                data = e.ActivityTaskCompletedEventAttributes
×
457

458
        case types.EventTypeActivityTaskFailed:
×
459
                data = e.ActivityTaskFailedEventAttributes
×
460

461
        case types.EventTypeActivityTaskTimedOut:
×
462
                data = e.ActivityTaskTimedOutEventAttributes
×
463

464
        case types.EventTypeActivityTaskCancelRequested:
×
465
                data = e.ActivityTaskCancelRequestedEventAttributes
×
466

467
        case types.EventTypeRequestCancelActivityTaskFailed:
×
468
                data = e.RequestCancelActivityTaskFailedEventAttributes
×
469

470
        case types.EventTypeActivityTaskCanceled:
×
471
                data = e.ActivityTaskCanceledEventAttributes
×
472

473
        case types.EventTypeTimerStarted:
×
474
                data = e.TimerStartedEventAttributes
×
475

476
        case types.EventTypeTimerFired:
×
477
                data = e.TimerFiredEventAttributes
×
478

479
        case types.EventTypeCancelTimerFailed:
×
480
                data = e.CancelTimerFailedEventAttributes
×
481

482
        case types.EventTypeTimerCanceled:
×
483
                data = e.TimerCanceledEventAttributes
×
484

485
        case types.EventTypeWorkflowExecutionCancelRequested:
×
486
                data = e.WorkflowExecutionCancelRequestedEventAttributes
×
487

488
        case types.EventTypeWorkflowExecutionCanceled:
×
489
                data = e.WorkflowExecutionCanceledEventAttributes
×
490

491
        case types.EventTypeRequestCancelExternalWorkflowExecutionInitiated:
×
492
                data = e.RequestCancelExternalWorkflowExecutionInitiatedEventAttributes
×
493

494
        case types.EventTypeRequestCancelExternalWorkflowExecutionFailed:
×
495
                data = e.RequestCancelExternalWorkflowExecutionFailedEventAttributes
×
496

497
        case types.EventTypeExternalWorkflowExecutionCancelRequested:
×
498
                data = e.ExternalWorkflowExecutionCancelRequestedEventAttributes
×
499

500
        case types.EventTypeMarkerRecorded:
×
501
                data = e.MarkerRecordedEventAttributes
×
502

503
        case types.EventTypeWorkflowExecutionSignaled:
×
504
                data = e.WorkflowExecutionSignaledEventAttributes
×
505

506
        case types.EventTypeWorkflowExecutionTerminated:
×
507
                data = e.WorkflowExecutionTerminatedEventAttributes
×
508

509
        case types.EventTypeWorkflowExecutionContinuedAsNew:
×
510
                data = e.WorkflowExecutionContinuedAsNewEventAttributes
×
511

512
        case types.EventTypeStartChildWorkflowExecutionInitiated:
×
513
                data = e.StartChildWorkflowExecutionInitiatedEventAttributes
×
514

515
        case types.EventTypeStartChildWorkflowExecutionFailed:
×
516
                data = e.StartChildWorkflowExecutionFailedEventAttributes
×
517

518
        case types.EventTypeChildWorkflowExecutionStarted:
×
519
                data = e.ChildWorkflowExecutionStartedEventAttributes
×
520

521
        case types.EventTypeChildWorkflowExecutionCompleted:
×
522
                data = e.ChildWorkflowExecutionCompletedEventAttributes
×
523

524
        case types.EventTypeChildWorkflowExecutionFailed:
×
525
                data = e.ChildWorkflowExecutionFailedEventAttributes
×
526

527
        case types.EventTypeChildWorkflowExecutionCanceled:
×
528
                data = e.ChildWorkflowExecutionCanceledEventAttributes
×
529

530
        case types.EventTypeChildWorkflowExecutionTimedOut:
×
531
                data = e.ChildWorkflowExecutionTimedOutEventAttributes
×
532

533
        case types.EventTypeChildWorkflowExecutionTerminated:
×
534
                data = e.ChildWorkflowExecutionTerminatedEventAttributes
×
535

536
        case types.EventTypeSignalExternalWorkflowExecutionInitiated:
×
537
                data = e.SignalExternalWorkflowExecutionInitiatedEventAttributes
×
538

539
        case types.EventTypeSignalExternalWorkflowExecutionFailed:
×
540
                data = e.SignalExternalWorkflowExecutionFailedEventAttributes
×
541

542
        case types.EventTypeExternalWorkflowExecutionSignaled:
×
543
                data = e.ExternalWorkflowExecutionSignaledEventAttributes
×
544

545
        default:
×
546
                data = e
×
547
        }
548
        return data
6✔
549
}
550

551
func isAttributeName(name string) bool {
104✔
552
        for i := types.EventType(0); i < types.EventType(40); i++ {
4,186✔
553
                if name == i.String()+"EventAttributes" {
4,084✔
554
                        return true
2✔
555
                }
2✔
556
        }
557
        return false
102✔
558
}
559

560
func getCurrentUserFromEnv() string {
×
561
        for _, n := range envKeysForUserName {
×
562
                if len(os.Getenv(n)) > 0 {
×
563
                        return os.Getenv(n)
×
564
                }
×
565
        }
566
        return "unknown"
×
567
}
568

569
func prettyPrintJSONObject(o interface{}) {
3✔
570
        b, err := json.MarshalIndent(o, "", "  ")
3✔
571
        if err != nil {
3✔
572
                fmt.Printf("Error when try to print pretty: %v\n", err)
×
573
                fmt.Println(o)
×
574
        }
×
575
        os.Stdout.Write(b)
3✔
576
        fmt.Println()
3✔
577
}
578

579
func mapKeysToArray(m map[string]string) []string {
3✔
580
        var out []string
3✔
581
        for k := range m {
24✔
582
                out = append(out, k)
21✔
583
        }
21✔
584
        return out
3✔
585
}
586

587
func intSliceToSet(s []int) map[int]struct{} {
×
588
        var ret = make(map[int]struct{}, len(s))
×
589
        for _, v := range s {
×
590
                ret[v] = struct{}{}
×
591
        }
×
592
        return ret
×
593
}
594

595
func printError(msg string, err error) {
24✔
596
        if err != nil {
46✔
597
                fmt.Printf("%s %s\n%s %+v\n", colorRed("Error:"), msg, colorMagenta("Error Details:"), err)
22✔
598
                if os.Getenv(showErrorStackEnv) != `` {
22✔
599
                        fmt.Printf("Stack trace:\n")
×
600
                        debug.PrintStack()
×
601
                } else {
22✔
602
                        fmt.Printf("('export %s=1' to see stack traces)\n", showErrorStackEnv)
22✔
603
                }
22✔
604
        } else {
2✔
605
                fmt.Printf("%s %s\n", colorRed("Error:"), msg)
2✔
606
        }
2✔
607
}
608

609
// ErrorAndExit print easy to understand error msg first then error detail in a new line
610
func ErrorAndExit(msg string, err error) {
24✔
611
        printError(msg, err)
24✔
612
        osExit(1)
24✔
613
}
24✔
614

615
func getWorkflowClient(c *cli.Context) frontend.Client {
41✔
616
        return cFactory.ServerFrontendClient(c)
41✔
617
}
41✔
618

619
func getRequiredOption(c *cli.Context, optionName string) string {
44✔
620
        value := c.String(optionName)
44✔
621
        if len(value) == 0 {
44✔
622
                ErrorAndExit(fmt.Sprintf("Option %s is required", optionName), nil)
×
623
        }
×
624
        return value
44✔
625
}
626

627
func getRequiredInt64Option(c *cli.Context, optionName string) int64 {
×
628
        if !c.IsSet(optionName) {
×
629
                ErrorAndExit(fmt.Sprintf("Option %s is required", optionName), nil)
×
630
        }
×
631
        return c.Int64(optionName)
×
632
}
633

634
func getRequiredIntOption(c *cli.Context, optionName string) int {
1✔
635
        if !c.IsSet(optionName) {
1✔
636
                ErrorAndExit(fmt.Sprintf("Option %s is required", optionName), nil)
×
637
        }
×
638
        return c.Int(optionName)
1✔
639
}
640

641
func getRequiredGlobalOption(c *cli.Context, optionName string) string {
59✔
642
        value := c.GlobalString(optionName)
59✔
643
        if len(value) == 0 {
59✔
644
                ErrorAndExit(fmt.Sprintf("Global option %s is required", optionName), nil)
×
645
        }
×
646
        return value
59✔
647
}
648

649
func timestampPtrToStringPtr(unixNanoPtr *int64, onlyTime bool) *string {
×
650
        if unixNanoPtr == nil {
×
651
                return nil
×
652
        }
×
653
        return common.StringPtr(convertTime(*unixNanoPtr, onlyTime))
×
654
}
655

656
func convertTime(unixNano int64, onlyTime bool) string {
16✔
657
        t := time.Unix(0, unixNano)
16✔
658
        var result string
16✔
659
        if onlyTime {
16✔
660
                result = t.Format(defaultTimeFormat)
×
661
        } else {
16✔
662
                result = t.Format(defaultDateTimeFormat)
16✔
663
        }
16✔
664
        return result
16✔
665
}
666

667
func parseTime(timeStr string, defaultValue int64) int64 {
49✔
668
        if len(timeStr) == 0 {
66✔
669
                return defaultValue
17✔
670
        }
17✔
671

672
        // try to parse
673
        parsedTime, err := time.Parse(defaultDateTimeFormat, timeStr)
32✔
674
        if err == nil {
33✔
675
                return parsedTime.UnixNano()
1✔
676
        }
1✔
677

678
        // treat as raw time
679
        resultValue, err := strconv.ParseInt(timeStr, 10, 64)
31✔
680
        if err == nil {
32✔
681
                return resultValue
1✔
682
        }
1✔
683

684
        // treat as time range format
685
        parsedTime, err = parseTimeRange(timeStr)
30✔
686
        if err != nil {
30✔
687
                ErrorAndExit(fmt.Sprintf("Cannot parse time '%s', use UTC format '2006-01-02T15:04:05Z', "+
×
688
                        "time range or raw UnixNano directly. See help for more details.", timeStr), err)
×
689
        }
×
690
        return parsedTime.UnixNano()
30✔
691
}
692

693
// parseTimeRange parses a given time duration string (in format X<time-duration>) and
694
// returns parsed timestamp given that duration in the past from current time.
695
// All valid values must contain a number followed by a time-duration, from the following list (long form/short form):
696
// - second/s
697
// - minute/m
698
// - hour/h
699
// - day/d
700
// - week/w
701
// - month/M
702
// - year/y
703
// For example, possible input values, and their result:
704
// - "3d" or "3day" --> three days --> time.Now().Add(-3 * 24 * time.Hour)
705
// - "2m" or "2minute" --> two minutes --> time.Now().Add(-2 * time.Minute)
706
// - "1w" or "1week" --> one week --> time.Now().Add(-7 * 24 * time.Hour)
707
// - "30s" or "30second" --> thirty seconds --> time.Now().Add(-30 * time.Second)
708
// Note: Duration strings are case-sensitive, and should be used as mentioned above only.
709
// Limitation: Value of numerical multiplier, X should be in b/w 0 - 1e6 (1 million), boundary values excluded i.e.
710
// 0 < X < 1e6. Also, the maximum time in the past can be 1 January 1970 00:00:00 UTC (epoch time),
711
// so giving "1000y" will result in epoch time.
712
func parseTimeRange(timeRange string) (time.Time, error) {
30✔
713
        match, err := regexp.MatchString(defaultDateTimeRangeShortRE, timeRange)
30✔
714
        if !match { // fallback on to check if it's of longer notation
44✔
715
                _, err = regexp.MatchString(defaultDateTimeRangeLongRE, timeRange)
14✔
716
        }
14✔
717
        if err != nil {
30✔
718
                return time.Time{}, err
×
719
        }
×
720

721
        re, _ := regexp.Compile(defaultDateTimeRangeNum)
30✔
722
        idx := re.FindStringSubmatchIndex(timeRange)
30✔
723
        if idx == nil {
30✔
724
                return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange)
×
725
        }
×
726

727
        num, err := strconv.Atoi(timeRange[idx[0]:idx[1]])
30✔
728
        if err != nil {
30✔
729
                return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange)
×
730
        }
×
731
        if num >= 1e6 {
30✔
732
                return time.Time{}, fmt.Errorf("invalid time-duation multiplier %d, allowed range is 0 < multiplier < 1000000", num)
×
733
        }
×
734

735
        dur, err := parseTimeDuration(timeRange[idx[1]:])
30✔
736
        if err != nil {
30✔
737
                return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange)
×
738
        }
×
739

740
        res := time.Now().Add(time.Duration(-num) * dur) // using server's local timezone
30✔
741
        epochTime := time.Unix(0, 0)
30✔
742
        if res.Before(epochTime) {
32✔
743
                res = epochTime
2✔
744
        }
2✔
745
        return res, nil
30✔
746
}
747

748
func parseSingleTs(ts string) (time.Time, error) {
×
749
        var tsOut time.Time
×
750
        var err error
×
751
        formats := []string{"2006-01-02T15:04:05", "2006-01-02T15:04", "2006-01-02", "2006-01-02T15:04:05+0700", time.RFC3339}
×
752
        for _, format := range formats {
×
753
                if tsOut, err = time.Parse(format, ts); err == nil {
×
754
                        return tsOut, err
×
755
                }
×
756
        }
757
        return tsOut, err
×
758
}
759

760
// parseTimeDuration parses the given time duration in either short or long convention
761
// and returns the time.Duration
762
// Valid values (long notation/short notation):
763
// - second/s
764
// - minute/m
765
// - hour/h
766
// - day/d
767
// - week/w
768
// - month/M
769
// - year/y
770
// NOTE: the input "duration" is case-sensitive
771
func parseTimeDuration(duration string) (dur time.Duration, err error) {
30✔
772
        switch duration {
30✔
773
        case "s", "second":
4✔
774
                dur = time.Second
4✔
775
        case "m", "minute":
4✔
776
                dur = time.Minute
4✔
777
        case "h", "hour":
4✔
778
                dur = time.Hour
4✔
779
        case "d", "day":
4✔
780
                dur = day
4✔
781
        case "w", "week":
4✔
782
                dur = week
4✔
783
        case "M", "month":
4✔
784
                dur = month
4✔
785
        case "y", "year":
6✔
786
                dur = year
6✔
787
        default:
×
788
                err = fmt.Errorf("unknown time duration %s", duration)
×
789
        }
790
        return
30✔
791
}
792

793
func strToTaskListType(str string) types.TaskListType {
2✔
794
        if strings.ToLower(str) == "activity" {
3✔
795
                return types.TaskListTypeActivity
1✔
796
        }
1✔
797
        return types.TaskListTypeDecision
1✔
798
}
799

800
func getCliIdentity() string {
15✔
801
        return fmt.Sprintf("cadence-cli@%s", getHostName())
15✔
802
}
15✔
803

804
func getHostName() string {
15✔
805
        hostName, err := os.Hostname()
15✔
806
        if err != nil {
15✔
807
                hostName = "UnKnown"
×
808
        }
×
809
        return hostName
15✔
810
}
811

812
func processJWTFlags(ctx context.Context, cliCtx *cli.Context) context.Context {
79✔
813
        path := getJWTPrivateKey(cliCtx)
79✔
814
        t := getJWT(cliCtx)
79✔
815
        var token string
79✔
816

79✔
817
        if t != "" {
79✔
818
                token = t
×
819
        } else if path != "" {
79✔
820
                createdToken, err := createJWT(path)
×
821
                if err != nil {
×
822
                        ErrorAndExit("Error creating JWT token", err)
×
823
                }
×
824
                token = *createdToken
×
825
        }
826

827
        ctx = context.WithValue(ctx, CtxKeyJWT, token)
79✔
828
        return ctx
79✔
829
}
830

831
func populateContextFromCLIContext(ctx context.Context, cliCtx *cli.Context) context.Context {
79✔
832
        ctx = processJWTFlags(ctx, cliCtx)
79✔
833
        return ctx
79✔
834
}
79✔
835

836
func newContext(c *cli.Context) (context.Context, context.CancelFunc) {
50✔
837
        return newTimedContext(c, defaultContextTimeout)
50✔
838
}
50✔
839

840
func newContextForLongPoll(c *cli.Context) (context.Context, context.CancelFunc) {
22✔
841
        return newTimedContext(c, defaultContextTimeoutForLongPoll)
22✔
842
}
22✔
843

844
func newIndefiniteContext(c *cli.Context) (context.Context, context.CancelFunc) {
7✔
845
        if c.GlobalIsSet(FlagContextTimeout) {
7✔
846
                return newTimedContext(c, time.Duration(c.GlobalInt(FlagContextTimeout))*time.Second)
×
847
        }
×
848

849
        return context.WithCancel(populateContextFromCLIContext(context.Background(), c))
7✔
850
}
851

852
func newTimedContext(c *cli.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
72✔
853
        if c.GlobalIsSet(FlagContextTimeout) {
72✔
854
                timeout = time.Duration(c.GlobalInt(FlagContextTimeout)) * time.Second
×
855
        }
×
856
        ctx := populateContextFromCLIContext(context.Background(), c)
72✔
857
        return context.WithTimeout(ctx, timeout)
72✔
858
}
859

860
// process and validate input provided through cmd or file
861
func processJSONInput(c *cli.Context) string {
11✔
862
        return processJSONInputHelper(c, jsonTypeInput)
11✔
863
}
11✔
864

865
// process and validate json
866
func processJSONInputHelper(c *cli.Context, jType jsonType) string {
23✔
867
        var flagNameOfRawInput string
23✔
868
        var flagNameOfInputFileName string
23✔
869

23✔
870
        switch jType {
23✔
871
        case jsonTypeInput:
11✔
872
                flagNameOfRawInput = FlagInput
11✔
873
                flagNameOfInputFileName = FlagInputFile
11✔
874
        case jsonTypeMemo:
6✔
875
                flagNameOfRawInput = FlagMemo
6✔
876
                flagNameOfInputFileName = FlagMemoFile
6✔
877
        case jsonTypeHeader:
6✔
878
                flagNameOfRawInput = FlagHeaderValue
6✔
879
                flagNameOfInputFileName = FlagHeaderFile
6✔
880
        case jsonTypeSignal:
×
881
                flagNameOfRawInput = FlagSignalInput
×
882
                flagNameOfInputFileName = FlagSignalInputFile
×
883
        default:
×
884
                return ""
×
885
        }
886

887
        var input string
23✔
888
        if c.IsSet(flagNameOfRawInput) {
23✔
889
                input = c.String(flagNameOfRawInput)
×
890
        } else if c.IsSet(flagNameOfInputFileName) {
23✔
891
                inputFile := c.String(flagNameOfInputFileName)
×
892
                // This method is purely used to parse input from the CLI. The input comes from a trusted user
×
893
                // #nosec
×
894
                data, err := ioutil.ReadFile(inputFile)
×
895
                if err != nil {
×
896
                        ErrorAndExit("Error reading input file", err)
×
897
                }
×
898
                input = string(data)
×
899
        }
900
        if input != "" {
23✔
901
                if err := validateJSONs(input); err != nil {
×
902
                        ErrorAndExit("Input is not valid JSON, or JSONs concatenated with spaces/newlines.", err)
×
903
                }
×
904
        }
905
        return input
23✔
906
}
907

908
func processMultipleKeys(rawKey, separator string) []string {
12✔
909
        var keys []string
12✔
910
        if strings.TrimSpace(rawKey) != "" {
12✔
911
                keys = strings.Split(rawKey, separator)
×
912
        }
×
913
        return keys
12✔
914
}
915

916
func processMultipleJSONValues(rawValue string) []string {
12✔
917
        var values []string
12✔
918
        var sc fastjson.Scanner
12✔
919
        sc.Init(rawValue)
12✔
920
        for sc.Next() {
12✔
921
                values = append(values, sc.Value().String())
×
922
        }
×
923
        if err := sc.Error(); err != nil {
12✔
924
                ErrorAndExit("Parse json error.", err)
×
925
        }
×
926
        return values
12✔
927
}
928

929
func mapFromKeysValues(keys, values []string) map[string][]byte {
12✔
930
        fields := map[string][]byte{}
12✔
931
        for i, key := range keys {
12✔
932
                fields[key] = []byte(values[i])
×
933
        }
×
934
        return fields
12✔
935
}
936

937
// validate whether str is a valid json or multi valid json concatenated with spaces/newlines
938
func validateJSONs(str string) error {
×
939
        input := []byte(str)
×
940
        dec := json.NewDecoder(bytes.NewReader(input))
×
941
        for {
×
942
                _, err := dec.Token()
×
943
                if err == io.EOF {
×
944
                        return nil // End of input, valid JSON
×
945
                }
×
946
                if err != nil {
×
947
                        return err // Invalid input
×
948
                }
×
949
        }
950
}
951

952
// use parseBool to ensure all BOOL search attributes only be "true" or "false"
953
func parseBool(str string) (bool, error) {
9✔
954
        switch str {
9✔
955
        case "true":
2✔
956
                return true, nil
2✔
957
        case "false":
2✔
958
                return false, nil
2✔
959
        }
960
        return false, fmt.Errorf("not parseable bool value: %s", str)
5✔
961
}
962

963
func trimSpace(strs []string) []string {
×
964
        result := make([]string, len(strs))
×
965
        for i, v := range strs {
×
966
                result[i] = strings.TrimSpace(v)
×
967
        }
×
968
        return result
×
969
}
970

971
func parseArray(v string) (interface{}, error) {
10✔
972
        if len(v) > 0 && v[0] == '[' && v[len(v)-1] == ']' {
17✔
973
                parsedValues, err := fastjson.Parse(v)
7✔
974
                if err != nil {
8✔
975
                        return nil, err
1✔
976
                }
1✔
977
                arr, err := parsedValues.Array()
6✔
978
                if err != nil {
6✔
979
                        return nil, err
×
980
                }
×
981
                result := make([]interface{}, len(arr))
6✔
982
                for i, item := range arr {
22✔
983
                        s := item.String()
16✔
984
                        if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' { // remove addition quote from json
26✔
985
                                s = s[1 : len(s)-1]
10✔
986
                                if sTime, err := time.Parse(defaultDateTimeFormat, s); err == nil {
12✔
987
                                        result[i] = sTime
2✔
988
                                        continue
2✔
989
                                }
990
                        }
991
                        result[i] = s
14✔
992
                }
993
                return result, nil
6✔
994
        }
995
        return nil, errors.New("not array")
3✔
996
}
997

998
func convertStringToRealType(v string) interface{} {
7✔
999
        var genVal interface{}
7✔
1000
        var err error
7✔
1001

7✔
1002
        if genVal, err = strconv.ParseInt(v, 10, 64); err == nil {
8✔
1003

1✔
1004
        } else if genVal, err = parseBool(v); err == nil {
9✔
1005

2✔
1006
        } else if genVal, err = strconv.ParseFloat(v, 64); err == nil {
7✔
1007

1✔
1008
        } else if genVal, err = time.Parse(defaultDateTimeFormat, v); err == nil {
5✔
1009

1✔
1010
        } else if genVal, err = parseArray(v); err == nil {
4✔
1011

1✔
1012
        } else {
2✔
1013
                genVal = v
1✔
1014
        }
1✔
1015

1016
        return genVal
7✔
1017
}
1018

1019
func truncate(str string) string {
3✔
1020
        if len(str) > maxOutputStringLength {
3✔
1021
                return str[:maxOutputStringLength]
×
1022
        }
×
1023
        return str
3✔
1024
}
1025

1026
// this only works for ANSI terminal, which means remove existing lines won't work if users redirect to file
1027
// ref: https://en.wikipedia.org/wiki/ANSI_escape_code
1028
func removePrevious2LinesFromTerminal() {
×
1029
        fmt.Printf("\033[1A")
×
1030
        fmt.Printf("\033[2K")
×
1031
        fmt.Printf("\033[1A")
×
1032
        fmt.Printf("\033[2K")
×
1033
}
×
1034

1035
func showNextPage() bool {
×
1036
        fmt.Printf("Press %s to show next page, press %s to quit: ",
×
1037
                color.GreenString("Enter"), color.RedString("any other key then Enter"))
×
1038
        var input string
×
1039
        fmt.Scanln(&input)
×
1040
        return strings.Trim(input, " ") == ""
×
1041
}
×
1042

1043
// prompt will show input msg, then waiting user input y/yes to continue
1044
func prompt(msg string) {
×
1045
        reader := bufio.NewReader(os.Stdin)
×
1046
        fmt.Println(msg)
×
1047
        text, _ := reader.ReadString('\n')
×
1048
        textLower := strings.ToLower(strings.TrimRight(text, "\n"))
×
1049
        if textLower != "y" && textLower != "yes" {
×
1050
                os.Exit(0)
×
1051
        }
×
1052
}
1053
func getInputFile(inputFile string) *os.File {
×
1054
        if len(inputFile) == 0 {
×
1055
                info, err := os.Stdin.Stat()
×
1056
                if err != nil {
×
1057
                        ErrorAndExit("Failed to stat stdin file handle", err)
×
1058
                }
×
1059
                if info.Mode()&os.ModeCharDevice != 0 || info.Size() <= 0 {
×
1060
                        fmt.Fprintln(os.Stderr, "Provide a filename or pass data to STDIN")
×
1061
                        os.Exit(1)
×
1062
                }
×
1063
                return os.Stdin
×
1064
        }
1065
        // This code is executed from the CLI. All user input is from a CLI user.
1066
        // #nosec
1067
        f, err := os.Open(inputFile)
×
1068
        if err != nil {
×
1069
                ErrorAndExit(fmt.Sprintf("Failed to open input file for reading: %v", inputFile), err)
×
1070
        }
×
1071
        return f
×
1072
}
1073

1074
// createJWT defines the logic to create a JWT
1075
func createJWT(keyPath string) (*string, error) {
×
1076
        claims := authorization.JWTClaims{
×
1077
                Admin: true,
×
1078
                Iat:   time.Now().Unix(),
×
1079
                TTL:   60 * 10,
×
1080
        }
×
1081

×
1082
        privateKey, err := common.LoadRSAPrivateKey(keyPath)
×
1083
        if err != nil {
×
1084
                return nil, err
×
1085
        }
×
1086

1087
        signer, err := jwt.NewSignerRS(jwt.RS256, privateKey)
×
1088
        if err != nil {
×
1089
                return nil, err
×
1090
        }
×
1091
        builder := jwt.NewBuilder(signer)
×
1092
        token, err := builder.Build(claims)
×
1093
        if token == nil {
×
1094
                return nil, err
×
1095
        }
×
1096
        tokenString := token.String()
×
1097
        return &tokenString, nil
×
1098
}
1099

1100
func getWorkflowMemo(input map[string]interface{}) (*types.Memo, error) {
1✔
1101
        if input == nil {
1✔
1102
                return nil, nil
×
1103
        }
×
1104

1105
        memo := make(map[string][]byte)
1✔
1106
        for k, v := range input {
2✔
1107
                memoBytes, err := json.Marshal(v)
1✔
1108
                if err != nil {
1✔
1109
                        return nil, fmt.Errorf("encode workflow memo error: %v", err.Error())
×
1110
                }
×
1111
                memo[k] = memoBytes
1✔
1112
        }
1113
        return &types.Memo{Fields: memo}, nil
1✔
1114
}
1115

1116
func serializeSearchAttributes(input map[string]interface{}) (*types.SearchAttributes, error) {
×
1117
        if input == nil {
×
1118
                return nil, nil
×
1119
        }
×
1120

1121
        attr := make(map[string][]byte)
×
1122
        for k, v := range input {
×
1123
                attrBytes, err := json.Marshal(v)
×
1124
                if err != nil {
×
1125
                        return nil, fmt.Errorf("encode search attribute [%s] error: %v", k, err)
×
1126
                }
×
1127
                attr[k] = attrBytes
×
1128
        }
1129
        return &types.SearchAttributes{IndexedFields: attr}, nil
×
1130
}
1131

1132
// parseIntMultiRange will parse string of multiple integer ranges separates by commas.
1133
// Single range can be an integer or inclusive range separated by dash.
1134
// The result is a sorted set union of integers.
1135
// Example: "3,8-8,5-6" -> [3,4,5,8]
1136
func parseIntMultiRange(s string) ([]int, error) {
9✔
1137
        set := map[int]struct{}{}
9✔
1138
        ranges := strings.Split(strings.TrimSpace(s), ",")
9✔
1139
        for _, r := range ranges {
21✔
1140
                r = strings.TrimSpace(r)
12✔
1141
                if len(r) == 0 {
14✔
1142
                        continue
2✔
1143
                }
1144
                parts := strings.Split(r, "-")
10✔
1145
                switch len(parts) {
10✔
1146
                case 1:
3✔
1147
                        i, err := strconv.Atoi(strings.TrimSpace(parts[0]))
3✔
1148
                        if err != nil {
4✔
1149
                                return nil, fmt.Errorf("single number %q: %v", r, err)
1✔
1150
                        }
1✔
1151
                        set[i] = struct{}{}
2✔
1152
                case 2:
6✔
1153
                        lower, err := strconv.Atoi(strings.TrimSpace(parts[0]))
6✔
1154
                        if err != nil {
7✔
1155
                                return nil, fmt.Errorf("lower range of %q: %v", r, err)
1✔
1156
                        }
1✔
1157
                        upper, err := strconv.Atoi(strings.TrimSpace(parts[1]))
5✔
1158
                        if err != nil {
6✔
1159
                                return nil, fmt.Errorf("upper range of %q: %v", r, err)
1✔
1160
                        }
1✔
1161
                        for i := lower; i <= upper; i++ {
16✔
1162
                                set[i] = struct{}{}
12✔
1163
                        }
12✔
1164
                default:
1✔
1165
                        return nil, fmt.Errorf("invalid range %q", r)
1✔
1166
                }
1167
        }
1168

1169
        result := []int{}
5✔
1170
        for i := range set {
17✔
1171
                result = append(result, i)
12✔
1172
        }
12✔
1173
        sort.Ints(result)
5✔
1174
        return result, nil
5✔
1175
}
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