Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Built-in Model Context Protocol server for AI agent integration. Copilot and oth
<td width="50%">

### 🔄 All HTTP Methods
GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS with JSON body support from inline data or files.
GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, and custom methods through `request <method> <url>` with JSON body support from inline data or files.

### 📊 Verbose Diagnostics
Request/response details, traceparent injection for distributed tracing, and redacted sensitive headers in logs.
Expand Down
19 changes: 19 additions & 0 deletions cli/docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ azd rest version
| `delete` | Execute a DELETE request |
| `head` | Execute a HEAD request |
| `options` | Execute an OPTIONS request |
| `request` | Execute a request with a custom HTTP method |
| `scope` | Preview the detected OAuth scope and auth mode for a URL |
| `version` | Display the extension version |

Expand Down Expand Up @@ -165,6 +166,24 @@ azd rest options <url> [flags]
azd rest options https://api.example.com/resource
```

### `azd rest request <method> <url>`

Execute a request with a custom HTTP method while keeping the same auth, retry, formatting, and safety behavior as the named method commands.

**Usage:**
```bash
azd rest request <method> <url> [flags]
```

**Examples:**
```bash
# Send an uncommon method name
azd rest request PURGE https://management.azure.com/... --api-version 2024-01-01

# Lowercase input is normalized before the request is sent
azd rest request merge https://api.example.com/resource --no-auth
```

---

## Global Flags
Expand Down
32 changes: 32 additions & 0 deletions cli/src/internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"fmt"
"os"
"strings"
"time"

"github.com/azure/azure-dev/cli/azd/pkg/azdext"
Expand Down Expand Up @@ -118,6 +119,36 @@ func NewHeadCommand() *cobra.Command { return newHTTPMethodCommand(httpMethods[5
// NewOptionsCommand returns the OPTIONS subcommand.
func NewOptionsCommand() *cobra.Command { return newHTTPMethodCommand(httpMethods[6]) }

// NewRequestCommand returns the generic request subcommand for uncommon HTTP methods.
func NewRequestCommand() *cobra.Command {
return &cobra.Command{
Use: "request <method> <url>",
Short: "Execute a request with a custom HTTP method",
Long: `Execute a request with any HTTP method while reusing the same authentication,
retry, formatting, and safety behavior as the named method commands.

Examples:
azd rest request PURGE https://management.azure.com/... --api-version 2024-01-01
azd rest request merge https://api.example.com/resource --no-auth`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
method, err := normalizeRequestMethod(args[0])
if err != nil {
return err
}
return executeRequest(cmd, method, args[1])
},
}
}

func normalizeRequestMethod(method string) (string, error) {
normalized := strings.ToUpper(strings.TrimSpace(method))
if normalized == "" {
return "", fmt.Errorf("method is required")
}
return normalized, nil
}

// NewRootCmd creates the root command for azd rest
func NewRootCmd() *cobra.Command {
rootCmd, _ := azdext.NewExtensionRootCommand(azdext.ExtensionCommandOptions{
Expand Down Expand Up @@ -235,6 +266,7 @@ Examples:

// Add non-HTTP-method subcommands
rootCmd.AddCommand(
NewRequestCommand(),
NewScopeCommand(),
azdext.NewVersionCommand("jongio.azd.rest", version.Version, &outputFormat),
azdext.NewMetadataCommand("1.0", "jongio.azd.rest", NewRootCmd),
Expand Down
49 changes: 48 additions & 1 deletion cli/src/internal/cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,59 @@ func TestNewRootCmd(t *testing.T) {
}
}

expectedCommands := []string{"get", "post", "put", "patch", "delete", "head", "options", "scope", "version"}
expectedCommands := []string{"get", "post", "put", "patch", "delete", "head", "options", "request", "scope", "version"}
for _, expected := range expectedCommands {
assert.True(t, subcommandNames[expected], "Subcommand %s should be present", expected)
}
}

func TestNormalizeRequestMethod(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{name: "uppercase is unchanged", input: "PURGE", want: "PURGE"},
{name: "lowercase is uppercased", input: "merge", want: "MERGE"},
{name: "spaces are trimmed", input: " link ", want: "LINK"},
{name: "empty is rejected", input: " ", wantErr: true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := normalizeRequestMethod(tt.input)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}

func TestRequestCommandExecutesCustomMethod(t *testing.T) {
resetGlobalFlags()
var gotMethod string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotMethod = r.Method
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"ok":true}`))
}))
defer server.Close()

noAuth = true
outputFile = filepath.Join(t.TempDir(), "response.json")

cmd := NewRequestCommand()
cmd.SetArgs([]string{"purge", server.URL + "/cache"})

require.NoError(t, cmd.Execute())
assert.Equal(t, "PURGE", gotMethod)
}

func TestNewRootCmd_SilentFlag(t *testing.T) {
resetGlobalFlags()
cmd := NewRootCmd()
Expand Down
2 changes: 2 additions & 0 deletions cli/src/internal/skills/azd-rest/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ azd rest <method> <url> [flags]

Supported HTTP methods: `get`, `post`, `put`, `patch`, `delete`, `head`, `options`

Use `azd rest request <method> <url>` for uncommon method names such as `PURGE`, `MERGE`, or `LINK`.

Use `azd rest scope <url>` to preview the detected OAuth scope and auth mode for a URL without sending a request.

## Flags
Expand Down
Loading