• 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

64.46
/cmd/api/cloud.go
1
package api
2

3
import (
4
        "fmt"
5
        "io"
6
        "os"
7
        "path/filepath"
8
        "regexp"
9
        "strings"
10

11
        "github.com/astronomer/astro-cli/config"
12
        "github.com/astronomer/astro-cli/context"
13
        "github.com/astronomer/astro-cli/pkg/ansi"
14
        "github.com/astronomer/astro-cli/pkg/domainutil"
15
        "github.com/astronomer/astro-cli/pkg/openapi"
16
        "github.com/spf13/cobra"
17
)
18

19
// CloudOptions holds all options for the cloud api command.
20
type CloudOptions struct {
21
        RequestOptions
22
}
23

24
// NewCloudCmd creates the 'astro api cloud' command.
25
//
26
//nolint:dupl
27
func NewCloudCmd(out io.Writer) *cobra.Command {
4✔
28
        opts := &CloudOptions{
4✔
29
                RequestOptions: RequestOptions{
4✔
30
                        Out:    out,
4✔
31
                        ErrOut: os.Stderr,
4✔
32
                        // specCache is initialized lazily when the domain is known
4✔
33
                },
4✔
34
        }
4✔
35

4✔
36
        cmd := &cobra.Command{
4✔
37
                Use:   "cloud <endpoint | operation-id>",
4✔
38
                Short: "Make authenticated requests to the Astro Cloud API",
4✔
39
                Long: `Make authenticated HTTP requests to the Astro Cloud API (api.astronomer.io).
4✔
40

4✔
41
The argument can be either:
4✔
42
  - A path of an Astro Cloud API endpoint (e.g., /organizations/{organizationId})
4✔
43
  - An operation ID from the API spec (e.g., GetDeployment, ListOrganizations)
4✔
44

4✔
45
Placeholder values {organizationId} and {workspaceId} will be replaced
4✔
46
with values from the current context. Other path parameters can be provided
4✔
47
using the -p/--path-param flag.
4✔
48

4✔
49
The default HTTP request method is GET normally and POST if any parameters
4✔
50
were added. Override the method with --method. When using an operation ID,
4✔
51
the method is auto-detected from the API spec.
4✔
52

4✔
53
Pass one or more -f/--raw-field values in key=value format to add static string
4✔
54
parameters to the request payload. To add non-string or placeholder-determined
4✔
55
values, see -F/--field below.
4✔
56

4✔
57
The -F/--field flag has magic type conversion based on the format of the value:
4✔
58
  - literal values true, false, null, and integer numbers get converted to
4✔
59
    appropriate JSON types;
4✔
60
  - if the value starts with @, the rest of the value is interpreted as a
4✔
61
    filename to read the value from. Pass - to read from standard input.
4✔
62

4✔
63
To pass nested parameters in the request payload, use key[subkey]=value syntax.
4✔
64
To pass nested values as arrays, declare multiple fields with key[]=value1.`,
4✔
65
                Example: `  # List all Cloud API endpoints
4✔
66
  astro api cloud ls
4✔
67

4✔
68
  # Get organization details (auto-injects organizationId)
4✔
69
  astro api cloud /organizations/{organizationId}
4✔
70

4✔
71
  # Use operation ID with path parameters
4✔
72
  astro api cloud GetDeployment -p deploymentId=abc123
4✔
73

4✔
74
  # Use operation ID (organizationId auto-injected from context)
4✔
75
  astro api cloud ListDeployments
4✔
76

4✔
77
  # List deployments with jq filter
4✔
78
  astro api cloud /organizations/{organizationId}/deployments --jq '.[].name'
4✔
79

4✔
80
  # Create a resource with typed fields
4✔
81
  astro api cloud -X POST /organizations/{organizationId}/workspaces \
4✔
82
    -F name=my-workspace \
4✔
83
    -F description="My new workspace"
4✔
84

4✔
85
  # Use Go template for output
4✔
86
  astro api cloud /organizations/{organizationId}/deployments \
4✔
87
    --template '{{range .}}{{.name}} ({{.status}}){{"\n"}}{{end}}'
4✔
88

4✔
89
  # Generate curl command instead of executing
4✔
90
  astro api cloud /organizations/{organizationId}/deployments --generate
4✔
91

4✔
92
  # Include response headers
4✔
93
  astro api cloud /organizations/{organizationId} -i
4✔
94

4✔
95
  # Verbose mode showing full request/response
4✔
96
  astro api cloud /organizations/{organizationId} --verbose`,
4✔
97
                Args: cobra.MaximumNArgs(1),
4✔
98
                PreRunE: func(cmd *cobra.Command, args []string) error {
4✔
NEW
99
                        opts.RequestMethodPassed = cmd.Flags().Changed("method")
×
NEW
100
                        return nil
×
NEW
101
                },
×
NEW
102
                RunE: func(cmd *cobra.Command, args []string) error {
×
NEW
103
                        if len(args) == 0 {
×
NEW
104
                                // Interactive mode - show endpoint selection
×
NEW
105
                                return runCloudInteractive(opts)
×
NEW
106
                        }
×
NEW
107
                        opts.RequestPath = args[0]
×
NEW
108
                        return runCloud(opts)
×
109
                },
110
        }
111

112
        // Request flags
113
        cmd.Flags().StringVarP(&opts.RequestMethod, "method", "X", "GET", "The HTTP method for the request")
4✔
114
        cmd.Flags().StringArrayVarP(&opts.MagicFields, "field", "F", nil, "Add a typed parameter in key=value format")
4✔
115
        cmd.Flags().StringArrayVarP(&opts.RawFields, "raw-field", "f", nil, "Add a string parameter in key=value format")
4✔
116
        cmd.Flags().StringArrayVarP(&opts.RequestHeaders, "header", "H", nil, "Add a HTTP request header in key:value format")
4✔
117
        cmd.Flags().StringVar(&opts.RequestInputFile, "input", "", "The file to use as body for the HTTP request (use \"-\" for stdin)")
4✔
118
        cmd.Flags().StringArrayVarP(&opts.PathParams, "path-param", "p", nil, "Path parameter in key=value format (for use with operation IDs)")
4✔
119

4✔
120
        // Output flags
4✔
121
        cmd.Flags().BoolVarP(&opts.ShowResponseHeaders, "include", "i", false, "Include HTTP response status line and headers in the output")
4✔
122
        cmd.Flags().BoolVar(&opts.Paginate, "paginate", false, "Make additional HTTP requests to fetch all pages of results")
4✔
123
        cmd.Flags().BoolVar(&opts.Slurp, "slurp", false, "Use with --paginate to return an array of all pages")
4✔
124
        cmd.Flags().BoolVar(&opts.Silent, "silent", false, "Do not print the response body")
4✔
125
        cmd.Flags().StringVarP(&opts.Template, "template", "t", "", "Format JSON output using a Go template")
4✔
126
        cmd.Flags().StringVarP(&opts.FilterOutput, "jq", "q", "", "Query to select values from the response using jq syntax")
4✔
127
        cmd.Flags().BoolVar(&opts.Verbose, "verbose", false, "Include full HTTP request and response in the output")
4✔
128

4✔
129
        // Other flags
4✔
130
        cmd.Flags().BoolVar(&opts.GenerateCurl, "generate", false, "Output a curl command instead of executing the request")
4✔
131

4✔
132
        // Add list and describe subcommands (cloud-specific to support lazy cache init)
4✔
133
        cmd.AddCommand(NewCloudListCmd(out, opts))
4✔
134
        cmd.AddCommand(NewCloudDescribeCmd(out, opts))
4✔
135

4✔
136
        return cmd
4✔
137
}
138

139
// runCloud executes the cloud API request.
NEW
140
func runCloud(opts *CloudOptions) error {
×
NEW
141
        // Check if we're in a cloud context
×
NEW
142
        if !context.IsCloudContext() {
×
NEW
143
                return fmt.Errorf("the 'astro api cloud' command is only available in cloud context. Run 'astro login' to connect to Astro Cloud")
×
NEW
144
        }
×
145

146
        // Get current context for auth and placeholders
NEW
147
        ctx, err := context.GetCurrentContext()
×
NEW
148
        if err != nil {
×
NEW
149
                return fmt.Errorf("getting current context: %w", err)
×
NEW
150
        }
×
151

152
        // Check for token
NEW
153
        if ctx.Token == "" {
×
NEW
154
                return fmt.Errorf("not authenticated. Run 'astro login' to authenticate")
×
NEW
155
        }
×
156

157
        // Initialize the spec cache for this domain
NEW
158
        if err := initCloudSpecCache(opts, &ctx); err != nil {
×
NEW
159
                return fmt.Errorf("initializing API spec: %w", err)
×
NEW
160
        }
×
161

162
        // Resolve operation ID to path if needed
NEW
163
        requestPath := opts.RequestPath
×
NEW
164
        method := opts.RequestMethod
×
NEW
165
        methodFromSpec := false
×
NEW
166

×
NEW
167
        if isOperationID(requestPath) {
×
NEW
168
                endpoint, err := resolveOperationID(opts.specCache, requestPath, "cloud")
×
NEW
169
                if err != nil {
×
NEW
170
                        return err
×
NEW
171
                }
×
NEW
172
                requestPath = endpoint.Path
×
NEW
173
                if !opts.RequestMethodPassed {
×
NEW
174
                        method = endpoint.Method
×
NEW
175
                        methodFromSpec = true
×
NEW
176
                }
×
177
        }
178

179
        // Apply path params from flags
NEW
180
        requestPath, err = applyPathParams(requestPath, opts.PathParams)
×
NEW
181
        if err != nil {
×
NEW
182
                return fmt.Errorf("applying path params: %w", err)
×
NEW
183
        }
×
184

185
        // Fill context placeholders in the path
NEW
186
        requestPath, err = fillPlaceholders(requestPath, &ctx)
×
NEW
187
        if err != nil {
×
NEW
188
                return fmt.Errorf("filling placeholders: %w", err)
×
NEW
189
        }
×
190

191
        // Check for any remaining unfilled path parameters
NEW
192
        if missing := findMissingPathParams(requestPath); len(missing) > 0 {
×
NEW
193
                return fmt.Errorf("missing path parameter(s): %s. Use -p/--path-param to provide them (e.g., -p %s=value)",
×
NEW
194
                        strings.Join(missing, ", "), missing[0])
×
NEW
195
        }
×
196

197
        // Parse fields into request body
NEW
198
        params, err := parseFields(opts.MagicFields, opts.RawFields)
×
NEW
199
        if err != nil {
×
NEW
200
                return fmt.Errorf("parsing fields: %w", err)
×
NEW
201
        }
×
202

203
        // Determine HTTP method (only override if not from spec and not explicitly passed)
NEW
204
        if !methodFromSpec && !opts.RequestMethodPassed && (len(params) > 0 || opts.RequestInputFile != "") {
×
NEW
205
                method = "POST"
×
NEW
206
        }
×
207

208
        // Build the full URL using the domain-derived base URL
NEW
209
        baseURL := ctx.GetPublicRESTAPIURL("v1")
×
NEW
210
        url := buildURL(baseURL, requestPath)
×
NEW
211

×
NEW
212
        // Generate curl command if requested
×
NEW
213
        if opts.GenerateCurl {
×
NEW
214
                return generateCurl(opts.Out, method, url, ctx.Token, opts.RequestHeaders, params, opts.RequestInputFile)
×
NEW
215
        }
×
216

217
        // Build and execute the request
NEW
218
        return executeRequest(&opts.RequestOptions, method, url, ctx.Token, params)
×
219
}
220

221
// isOperationID checks if the input looks like an operation ID rather than a path.
222
// Operation IDs don't contain "/" and typically are CamelCase or camelCase.
223
func isOperationID(input string) bool {
8✔
224
        return !strings.Contains(input, "/")
8✔
225
}
8✔
226

227
// resolveOperationID looks up an operation ID in the OpenAPI spec and returns the endpoint.
228
// If no exact operation ID match is found, it falls back to trying the input as a path
229
// (with "/" prepended) to handle bare path segments like "version" or "health".
230
// cmdName is used only in the error message (e.g. "cloud", "airflow").
231
func resolveOperationID(specCache *openapi.Cache, operationID, cmdName string) (*openapi.Endpoint, error) {
4✔
232
        if err := specCache.Load(false); err != nil {
4✔
NEW
233
                return nil, fmt.Errorf("loading OpenAPI spec: %w", err)
×
NEW
234
        }
×
235

236
        endpoints := specCache.GetEndpoints()
4✔
237

4✔
238
        // Try as operation ID first
4✔
239
        endpoint := openapi.FindEndpointByOperationID(endpoints, operationID)
4✔
240
        if endpoint != nil {
6✔
241
                return endpoint, nil
2✔
242
        }
2✔
243

244
        // Fall back to trying as a path (e.g., "version" -> "/version")
245
        pathMatches := openapi.FindEndpointByPath(endpoints, "/"+operationID)
2✔
246
        if len(pathMatches) > 0 {
3✔
247
                return &pathMatches[0], nil
1✔
248
        }
1✔
249

250
        return nil, fmt.Errorf("'%s' not found as operation ID or path. Use 'astro api %s ls' to see available endpoints", operationID, cmdName)
1✔
251
}
252

253
// applyPathParams replaces path placeholders with values from --path-param flags.
254
func applyPathParams(path string, pathParams []string) (string, error) {
7✔
255
        if len(pathParams) == 0 {
10✔
256
                return path, nil
3✔
257
        }
3✔
258

259
        // Parse path params into a map
260
        params := make(map[string]string)
4✔
261
        for _, p := range pathParams {
9✔
262
                parts := strings.SplitN(p, "=", 2)
5✔
263
                if len(parts) != 2 {
6✔
264
                        return "", fmt.Errorf("invalid path param format '%s', expected key=value", p)
1✔
265
                }
1✔
266
                params[parts[0]] = parts[1]
4✔
267
        }
268

269
        // Replace placeholders in the path
270
        result := placeholderRE.ReplaceAllStringFunc(path, func(match string) string {
8✔
271
                name := match[1 : len(match)-1]
5✔
272
                if val, ok := params[name]; ok {
9✔
273
                        return val
4✔
274
                }
4✔
275
                return match
1✔
276
        })
277

278
        return result, nil
3✔
279
}
280

281
// runCloudInteractive runs the cloud API command in interactive mode.
NEW
282
func runCloudInteractive(opts *CloudOptions) error {
×
NEW
283
        // Check if we're in a cloud context
×
NEW
284
        if !context.IsCloudContext() {
×
NEW
285
                return fmt.Errorf("the 'astro api cloud' command is only available in cloud context. Run 'astro login' to connect to Astro Cloud")
×
NEW
286
        }
×
287

288
        // Get current context for cache initialization
NEW
289
        ctx, err := context.GetCurrentContext()
×
NEW
290
        if err != nil {
×
NEW
291
                return fmt.Errorf("getting current context: %w", err)
×
NEW
292
        }
×
293

294
        // Initialize the spec cache for this domain
NEW
295
        if err := initCloudSpecCache(opts, &ctx); err != nil {
×
NEW
296
                return fmt.Errorf("initializing API spec: %w", err)
×
NEW
297
        }
×
298

299
        // Load OpenAPI spec
NEW
300
        if err := opts.specCache.Load(false); err != nil {
×
NEW
301
                return fmt.Errorf("loading OpenAPI spec: %w", err)
×
NEW
302
        }
×
303

NEW
304
        endpoints := opts.specCache.GetEndpoints()
×
NEW
305
        if len(endpoints) == 0 {
×
NEW
306
                return fmt.Errorf("no endpoints found in API specification")
×
NEW
307
        }
×
308

309
        // Show endpoint selection
NEW
310
        fmt.Fprintf(opts.Out, "\nFound %d endpoints. Use '%s' to list them.\n", len(endpoints), ansi.Bold("astro api cloud ls"))
×
NEW
311
        fmt.Fprintf(opts.Out, "Run '%s' to make a request.\n\n", ansi.Bold("astro api cloud <endpoint>"))
×
NEW
312

×
NEW
313
        return nil
×
314
}
315

316
// placeholderRE matches placeholders like {organizationId}, {workspaceId}, {dag_id}, etc.
317
var placeholderRE = regexp.MustCompile(`\{([a-zA-Z][a-zA-Z0-9_]*)\}`)
318

319
// fillPlaceholders replaces placeholders with values from the context.
320
func fillPlaceholders(path string, ctx *config.Context) (string, error) {
8✔
321
        var errs []string
8✔
322

8✔
323
        result := placeholderRE.ReplaceAllStringFunc(path, func(match string) string {
16✔
324
                // Extract the name without braces
8✔
325
                name := match[1 : len(match)-1]
8✔
326

8✔
327
                switch strings.ToLower(name) {
8✔
328
                case "organizationid":
4✔
329
                        if ctx.Organization == "" {
5✔
330
                                errs = append(errs, "organizationId not set in context (run 'astro organization switch')")
1✔
331
                                return match
1✔
332
                        }
1✔
333
                        return ctx.Organization
3✔
334
                case "workspaceid":
3✔
335
                        if ctx.Workspace == "" {
4✔
336
                                errs = append(errs, "workspaceId not set in context (run 'astro workspace switch')")
1✔
337
                                return match
1✔
338
                        }
1✔
339
                        return ctx.Workspace
2✔
340
                default:
1✔
341
                        // Keep unknown placeholders as-is (user might provide them)
1✔
342
                        return match
1✔
343
                }
344
        })
345

346
        if len(errs) > 0 {
10✔
347
                return result, fmt.Errorf("placeholder error: %s", strings.Join(errs, "; "))
2✔
348
        }
2✔
349

350
        return result, nil
6✔
351
}
352

353
// findMissingPathParams returns any unfilled path parameters in the path.
354
func findMissingPathParams(path string) []string {
6✔
355
        matches := placeholderRE.FindAllStringSubmatch(path, -1)
6✔
356
        if len(matches) == 0 {
10✔
357
                return nil
4✔
358
        }
4✔
359

360
        missing := make([]string, 0, len(matches))
2✔
361
        for _, match := range matches {
5✔
362
                if len(match) > 1 {
6✔
363
                        missing = append(missing, match[1])
3✔
364
                }
3✔
365
        }
366
        return missing
2✔
367
}
368

369
// initCloudSpecCache initializes the OpenAPI spec cache for the current cloud
370
// context's domain. If the cache is already initialized, this is a no-op.
371
func initCloudSpecCache(opts *CloudOptions, ctx *config.Context) error {
3✔
372
        if opts.specCache != nil {
4✔
373
                return nil
1✔
374
        }
1✔
375
        domain := domainutil.FormatDomain(ctx.Domain)
2✔
376
        specURL := ctx.GetPublicRESTAPIURL("spec/v1.0")
2✔
377
        if specURL == "" {
2✔
NEW
378
                return fmt.Errorf("could not determine API spec URL for domain %q. Check your login context", ctx.Domain)
×
NEW
379
        }
×
380
        cachePath := filepath.Join(config.HomeConfigPath, openapi.CloudCacheFileNameForDomain(domain))
2✔
381
        opts.specCache = openapi.NewCacheWithOptions(specURL, cachePath)
2✔
382
        return nil
2✔
383
}
384

385
// NewCloudListCmd creates the 'astro api cloud ls' command.
386
// It lazily initializes the spec cache so the correct domain-specific spec URL is used.
387
func NewCloudListCmd(out io.Writer, parentOpts *CloudOptions) *cobra.Command {
4✔
388
        var verbose bool
4✔
389
        var refresh bool
4✔
390

4✔
391
        cmd := &cobra.Command{
4✔
392
                Use:     "ls [filter]",
4✔
393
                Aliases: []string{"list"},
4✔
394
                Short:   "List available Astro Cloud API endpoints",
4✔
395
                Long: `List all available endpoints from the Astro Cloud API.
4✔
396

4✔
397
You can optionally provide a filter to search for specific endpoints.
4✔
398
The filter matches against endpoint paths, methods, operation IDs, summaries, and tags.`,
4✔
399
                Example: `  # List all endpoints
4✔
400
  astro api cloud ls
4✔
401

4✔
402
  # Filter endpoints
4✔
403
  astro api cloud ls deployments
4✔
404

4✔
405
  # List POST endpoints
4✔
406
  astro api cloud ls POST
4✔
407

4✔
408
  # Show verbose output with descriptions
4✔
409
  astro api cloud ls --verbose`,
4✔
410
                Args: cobra.MaximumNArgs(1),
4✔
411
                RunE: func(cmd *cobra.Command, args []string) error {
4✔
NEW
412
                        var filter string
×
NEW
413
                        if len(args) > 0 {
×
NEW
414
                                filter = args[0]
×
NEW
415
                        }
×
416

NEW
417
                        if !context.IsCloudContext() {
×
NEW
418
                                return fmt.Errorf("the 'astro api cloud' command is only available in cloud context. Run 'astro login' to connect to Astro Cloud")
×
NEW
419
                        }
×
420

NEW
421
                        ctx, err := context.GetCurrentContext()
×
NEW
422
                        if err != nil {
×
NEW
423
                                return fmt.Errorf("getting current context: %w", err)
×
NEW
424
                        }
×
425

NEW
426
                        if err := initCloudSpecCache(parentOpts, &ctx); err != nil {
×
NEW
427
                                return fmt.Errorf("initializing API spec: %w", err)
×
NEW
428
                        }
×
429

NEW
430
                        listOpts := &ListOptions{
×
NEW
431
                                Out:       out,
×
NEW
432
                                specCache: parentOpts.specCache,
×
NEW
433
                                Filter:    filter,
×
NEW
434
                                Verbose:   verbose,
×
NEW
435
                                Refresh:   refresh,
×
NEW
436
                        }
×
NEW
437
                        return runList(listOpts)
×
438
                },
439
        }
440

441
        cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Show additional details like summaries and tags")
4✔
442
        cmd.Flags().BoolVar(&refresh, "refresh", false, "Force refresh of the OpenAPI specification cache")
4✔
443

4✔
444
        return cmd
4✔
445
}
446

447
// NewCloudDescribeCmd creates the 'astro api cloud describe' command.
448
// It lazily initializes the spec cache so the correct domain-specific spec URL is used.
449
func NewCloudDescribeCmd(out io.Writer, parentOpts *CloudOptions) *cobra.Command {
4✔
450
        var method string
4✔
451
        var refresh bool
4✔
452
        var verbose bool
4✔
453

4✔
454
        cmd := &cobra.Command{
4✔
455
                Use:   "describe <endpoint>",
4✔
456
                Short: "Describe an Astro Cloud API endpoint's request and response schema",
4✔
457
                Long: `Show detailed information about an Astro Cloud API endpoint, including:
4✔
458
- Path and query parameters
4✔
459
- Request body schema (for POST/PUT/PATCH)
4✔
460
- Response schema
4✔
461

4✔
462
The endpoint can be specified as a path or as an operation ID.`,
4✔
463
                Example: `  # Describe an endpoint by path
4✔
464
  astro api cloud describe /organizations/{organizationId}/deployments
4✔
465

4✔
466
  # Describe a POST endpoint specifically
4✔
467
  astro api cloud describe /organizations/{organizationId}/deployments -X POST
4✔
468

4✔
469
  # Describe by operation ID
4✔
470
  astro api cloud describe CreateDeployment`,
4✔
471
                Args: cobra.ExactArgs(1),
4✔
472
                RunE: func(cmd *cobra.Command, args []string) error {
4✔
NEW
473
                        if !context.IsCloudContext() {
×
NEW
474
                                return fmt.Errorf("the 'astro api cloud' command is only available in cloud context. Run 'astro login' to connect to Astro Cloud")
×
NEW
475
                        }
×
476

NEW
477
                        ctx, err := context.GetCurrentContext()
×
NEW
478
                        if err != nil {
×
NEW
479
                                return fmt.Errorf("getting current context: %w", err)
×
NEW
480
                        }
×
481

NEW
482
                        if err := initCloudSpecCache(parentOpts, &ctx); err != nil {
×
NEW
483
                                return fmt.Errorf("initializing API spec: %w", err)
×
NEW
484
                        }
×
485

NEW
486
                        descOpts := &DescribeOptions{
×
NEW
487
                                Out:       out,
×
NEW
488
                                specCache: parentOpts.specCache,
×
NEW
489
                                Endpoint:  args[0],
×
NEW
490
                                Method:    method,
×
NEW
491
                                Refresh:   refresh,
×
NEW
492
                                Verbose:   verbose,
×
NEW
493
                        }
×
NEW
494
                        return runDescribe(descOpts)
×
495
                },
496
        }
497

498
        cmd.Flags().StringVarP(&method, "method", "X", "", "HTTP method (GET, POST, PUT, PATCH, DELETE)")
4✔
499
        cmd.Flags().BoolVar(&refresh, "refresh", false, "Force refresh of the OpenAPI specification cache")
4✔
500
        cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Show spec URL and additional details")
4✔
501

4✔
502
        return cmd
4✔
503
}
504

505
// buildURL constructs the full URL from base and path.
506
func buildURL(baseURL, path string) string {
6✔
507
        // Ensure path starts with /
6✔
508
        if !strings.HasPrefix(path, "/") {
8✔
509
                path = "/" + path
2✔
510
        }
2✔
511
        return strings.TrimSuffix(baseURL, "/") + path
6✔
512
}
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