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

astronomer / astro-cli / 7ad3521d-16b3-40c1-8bae-3435cbf5c859

14 Feb 2026 02:21AM UTC coverage: 35.009% (+1.9%) from 33.15%
7ad3521d-16b3-40c1-8bae-3435cbf5c859

Pull #2006

circleci

jeremybeard
Use kin-openapi for OpenAPI library
Pull Request #2006: Add `astro api` command

2029 of 2451 new or added lines in 13 files covered. (82.78%)

3 existing lines in 1 file now uncovered.

22893 of 65392 relevant lines covered (35.01%)

8.43 hits per line

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

93.38
/cmd/api/fields.go
1
package api
2

3
import (
4
        "fmt"
5
        "io"
6
        "os"
7
        "strconv"
8
        "strings"
9
)
10

11
const (
12
        keyStart     = '['
13
        keyEnd       = ']'
14
        keySeparator = '='
15
)
16

17
// parseFields parses magic fields and raw fields into a map.
18
// Magic fields (-F) support type conversion and file reading.
19
// Raw fields (-f) are always treated as strings.
20
func parseFields(magicFields, rawFields []string) (map[string]interface{}, error) {
22✔
21
        params := make(map[string]interface{})
22✔
22

22✔
23
        // Parse raw fields first (string-only)
22✔
24
        for _, f := range rawFields {
41✔
25
                if err := parseField(params, f, false); err != nil {
23✔
26
                        return params, err
4✔
27
                }
4✔
28
        }
29

30
        // Parse magic fields (with type conversion)
31
        for _, f := range magicFields {
26✔
32
                if err := parseField(params, f, true); err != nil {
9✔
33
                        return params, err
1✔
34
                }
1✔
35
        }
36

37
        return params, nil
17✔
38
}
39

40
// parseField parses a single field and adds it to the params map.
41
//
42
//nolint:gocognit // Complex parsing logic for nested field syntax
43
func parseField(params map[string]interface{}, f string, isMagic bool) error {
27✔
44
        var valueIndex int
27✔
45
        var keystack []string
27✔
46
        keyStartAt := 0
27✔
47

27✔
48
parseLoop:
27✔
49
        for i, r := range f {
250✔
50
                switch r {
223✔
51
                case keyStart:
14✔
52
                        if keyStartAt == 0 {
25✔
53
                                keystack = append(keystack, f[0:i])
11✔
54
                        }
11✔
55
                        keyStartAt = i + 1
14✔
56
                case keyEnd:
14✔
57
                        keystack = append(keystack, f[keyStartAt:i])
14✔
58
                case keySeparator:
25✔
59
                        if keyStartAt == 0 {
40✔
60
                                keystack = append(keystack, f[0:i])
15✔
61
                        }
15✔
62
                        valueIndex = i + 1
25✔
63
                        break parseLoop
25✔
64
                }
65
        }
66

67
        if len(keystack) == 0 {
28✔
68
                return fmt.Errorf("invalid key: %q", f)
1✔
69
        }
1✔
70

71
        key := f
26✔
72
        var value interface{}
26✔
73
        if valueIndex == 0 {
27✔
74
                if keystack[len(keystack)-1] != "" {
1✔
NEW
75
                        return fmt.Errorf("field %q requires a value separated by an '=' sign", key)
×
NEW
76
                }
×
77
                // Empty array notation: key[]
78
                value = nil
1✔
79
        } else {
25✔
80
                key = f[0 : valueIndex-1]
25✔
81
                value = f[valueIndex:]
25✔
82
        }
25✔
83

84
        if isMagic && value != nil {
34✔
85
                var err error
8✔
86
                value, err = magicFieldValue(value.(string))
8✔
87
                if err != nil {
9✔
88
                        return fmt.Errorf("error parsing %q value: %w", key, err)
1✔
89
                }
1✔
90
        }
91

92
        destMap := params
25✔
93
        isArray := false
25✔
94
        var subkey string
25✔
95

25✔
96
        for _, k := range keystack {
64✔
97
                if k == "" {
46✔
98
                        isArray = true
7✔
99
                        continue
7✔
100
                }
101
                if subkey != "" {
39✔
102
                        var err error
7✔
103
                        if isArray {
9✔
104
                                destMap, err = addParamsSlice(destMap, subkey, k)
2✔
105
                                isArray = false
2✔
106
                        } else {
7✔
107
                                destMap, err = addParamsMap(destMap, subkey)
5✔
108
                        }
5✔
109
                        if err != nil {
8✔
110
                                return err
1✔
111
                        }
1✔
112
                }
113
                subkey = k
31✔
114
        }
115

116
        if isArray {
29✔
117
                if value == nil {
6✔
118
                        destMap[subkey] = []interface{}{}
1✔
119
                } else {
5✔
120
                        if v, exists := destMap[subkey]; exists {
6✔
121
                                if existSlice, ok := v.([]interface{}); ok {
3✔
122
                                        destMap[subkey] = append(existSlice, value)
1✔
123
                                } else {
2✔
124
                                        return fmt.Errorf("expected array type under %q, got %T", subkey, v)
1✔
125
                                }
1✔
126
                        } else {
2✔
127
                                destMap[subkey] = []interface{}{value}
2✔
128
                        }
2✔
129
                }
130
        } else {
19✔
131
                if _, exists := destMap[subkey]; exists {
20✔
132
                        return fmt.Errorf("unexpected override existing field under %q", subkey)
1✔
133
                }
1✔
134
                destMap[subkey] = value
18✔
135
        }
136

137
        return nil
22✔
138
}
139

140
// addParamsMap ensures a nested map exists at the given key and returns it.
141
func addParamsMap(m map[string]interface{}, key string) (map[string]interface{}, error) {
5✔
142
        if v, exists := m[key]; exists {
6✔
143
                if existMap, ok := v.(map[string]interface{}); ok {
1✔
NEW
144
                        return existMap, nil
×
NEW
145
                }
×
146
                return nil, fmt.Errorf("expected map type under %q, got %T", key, v)
1✔
147
        }
148
        newMap := make(map[string]interface{})
4✔
149
        m[key] = newMap
4✔
150
        return newMap, nil
4✔
151
}
152

153
// addParamsSlice handles adding to an array of objects.
154
func addParamsSlice(m map[string]interface{}, prevkey, newkey string) (map[string]interface{}, error) {
2✔
155
        if v, exists := m[prevkey]; exists {
3✔
156
                if existSlice, ok := v.([]interface{}); ok {
2✔
157
                        if len(existSlice) > 0 {
2✔
158
                                lastItem := existSlice[len(existSlice)-1]
1✔
159
                                if lastMap, ok := lastItem.(map[string]interface{}); ok {
2✔
160
                                        if _, keyExists := lastMap[newkey]; !keyExists {
1✔
NEW
161
                                                return lastMap, nil
×
NEW
162
                                        }
×
163
                                }
164
                        }
165
                        newMap := make(map[string]interface{})
1✔
166
                        m[prevkey] = append(existSlice, newMap)
1✔
167
                        return newMap, nil
1✔
168
                }
NEW
169
                return nil, fmt.Errorf("expected array type under %q, got %T", prevkey, v)
×
170
        }
171
        newMap := make(map[string]interface{})
1✔
172
        m[prevkey] = []interface{}{newMap}
1✔
173
        return newMap, nil
1✔
174
}
175

176
// magicFieldValue converts a string value to its appropriate type.
177
// Supports: true, false, null, integers, and file reading with @.
178
func magicFieldValue(v string) (interface{}, error) {
19✔
179
        // File reading
19✔
180
        if strings.HasPrefix(v, "@") {
22✔
181
                return readFileValue(v[1:])
3✔
182
        }
3✔
183

184
        // Integer conversion
185
        if n, err := strconv.Atoi(v); err == nil {
21✔
186
                return n, nil
5✔
187
        }
5✔
188

189
        // Boolean and null conversion
190
        switch v {
11✔
191
        case "true":
3✔
192
                return true, nil
3✔
193
        case "false":
2✔
194
                return false, nil
2✔
195
        case "null":
2✔
196
                return nil, nil
2✔
197
        default:
4✔
198
                return v, nil
4✔
199
        }
200
}
201

202
// readFileValue reads a value from a file or stdin.
203
func readFileValue(filename string) (string, error) {
3✔
204
        var r io.Reader
3✔
205
        if filename == "-" {
3✔
NEW
206
                r = os.Stdin
×
207
        } else {
3✔
208
                f, err := os.Open(filename)
3✔
209
                if err != nil {
5✔
210
                        return "", fmt.Errorf("opening file %q: %w", filename, err)
2✔
211
                }
2✔
212
                defer f.Close()
1✔
213
                r = f
1✔
214
        }
215

216
        b, err := io.ReadAll(r)
1✔
217
        if err != nil {
1✔
NEW
218
                return "", fmt.Errorf("reading file: %w", err)
×
NEW
219
        }
×
220

221
        return string(b), nil
1✔
222
}
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