Skip to content

Commit e4fdff5

Browse files
committed
fix: API7 EE compatibility — unwrap value envelope, route paths, error_msg field
Round 5 fixes for e2e tests against API7 Enterprise Edition: - Add unwrapValueEnvelope() to strip {"value": {...}} response wrapper - Fix APIError json tag from "message" to "error_msg" - Add Route.Paths field, --path flag, backward-compat --uri conversion - Add --service-id flag to route list command - Accept positional ID arg in credential create - Skip plugins without metadata_schema in config dump - Update all e2e test fixtures: routes use name+paths format - Global rule update test uses single plugin (API7 EE constraint) - Update all unit test mocks to use error_msg field
1 parent a978512 commit e4fdff5

File tree

15 files changed

+185
-54
lines changed

15 files changed

+185
-54
lines changed

pkg/api/client.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,30 @@ func (c *Client) do(method, path string, query map[string]string, body interface
132132
return nil, &apiErr
133133
}
134134

135-
return respBody, nil
135+
// API7 EE wraps single-resource responses in {"value": {...}}.
136+
// List responses use {"total": N, "list": [...]}, left as-is.
137+
return unwrapValueEnvelope(respBody), nil
138+
}
139+
140+
// unwrapValueEnvelope strips the API7 EE {"value": ...} envelope from
141+
// single-resource responses. List responses and non-object bodies pass through.
142+
func unwrapValueEnvelope(body []byte) []byte {
143+
trimmed := bytes.TrimSpace(body)
144+
if len(trimmed) == 0 || trimmed[0] != '{' {
145+
return body
146+
}
147+
148+
var envelope map[string]json.RawMessage
149+
if err := json.Unmarshal(body, &envelope); err != nil {
150+
return body
151+
}
152+
153+
val, hasValue := envelope["value"]
154+
_, hasList := envelope["list"]
155+
_, hasTotal := envelope["total"]
156+
if hasValue && !hasList && !hasTotal && len(envelope) <= 2 {
157+
return val
158+
}
159+
160+
return body
136161
}

pkg/api/client_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ func TestClient_APIError(t *testing.T) {
179179
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
180180
w.WriteHeader(http.StatusBadRequest)
181181
w.Header().Set("Content-Type", "application/json")
182-
w.Write([]byte(`{"message":"bad request"}`))
182+
w.Write([]byte(`{"error_msg":"bad request"}`))
183183
}))
184184
defer server.Close()
185185

@@ -213,7 +213,7 @@ func TestClient_APIError_401(t *testing.T) {
213213
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
214214
w.WriteHeader(http.StatusUnauthorized)
215215
w.Header().Set("Content-Type", "application/json")
216-
w.Write([]byte(`{"message":"invalid api key"}`))
216+
w.Write([]byte(`{"error_msg":"invalid api key"}`))
217217
}))
218218
defer server.Close()
219219

@@ -884,7 +884,7 @@ func TestClient_StatusCode_204(t *testing.T) {
884884
func TestClient_StatusCode_400(t *testing.T) {
885885
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
886886
w.WriteHeader(http.StatusBadRequest)
887-
w.Write([]byte(`{"message": "bad request"}`))
887+
w.Write([]byte(`{"error_msg": "bad request"}`))
888888
}))
889889
defer server.Close()
890890

@@ -904,7 +904,7 @@ func TestClient_StatusCode_400(t *testing.T) {
904904
func TestClient_StatusCode_403(t *testing.T) {
905905
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
906906
w.WriteHeader(http.StatusForbidden)
907-
w.Write([]byte(`{"message": "forbidden"}`))
907+
w.Write([]byte(`{"error_msg": "forbidden"}`))
908908
}))
909909
defer server.Close()
910910

@@ -933,7 +933,7 @@ func TestClient_StatusCode_403(t *testing.T) {
933933
func TestClient_StatusCode_404(t *testing.T) {
934934
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
935935
w.WriteHeader(http.StatusNotFound)
936-
w.Write([]byte(`{"message": "not found"}`))
936+
w.Write([]byte(`{"error_msg": "not found"}`))
937937
}))
938938
defer server.Close()
939939

@@ -962,7 +962,7 @@ func TestClient_StatusCode_404(t *testing.T) {
962962
func TestClient_StatusCode_500(t *testing.T) {
963963
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
964964
w.WriteHeader(http.StatusInternalServerError)
965-
w.Write([]byte(`{"message": "internal error"}`))
965+
w.Write([]byte(`{"error_msg": "internal error"}`))
966966
}))
967967
defer server.Close()
968968

@@ -991,7 +991,7 @@ func TestClient_StatusCode_500(t *testing.T) {
991991
func TestClient_StatusCode_502(t *testing.T) {
992992
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
993993
w.WriteHeader(http.StatusBadGateway)
994-
w.Write([]byte(`{"message": "bad gateway"}`))
994+
w.Write([]byte(`{"error_msg": "bad gateway"}`))
995995
}))
996996
defer server.Close()
997997

pkg/api/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type SingleResponse[T any] struct {
1919
// APIError represents an error response from the API7 EE Admin API.
2020
type APIError struct {
2121
StatusCode int `json:"-"`
22-
ErrorMsg string `json:"message"`
22+
ErrorMsg string `json:"error_msg"`
2323
}
2424

2525
func (e *APIError) Error() string {

pkg/api/types_route.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ type Route struct {
77
Desc string `json:"desc,omitempty" yaml:"desc,omitempty"`
88
URI string `json:"uri,omitempty" yaml:"uri,omitempty"`
99
URIs []string `json:"uris,omitempty" yaml:"uris,omitempty"`
10+
Paths []string `json:"paths,omitempty" yaml:"paths,omitempty"`
1011
Methods []string `json:"methods,omitempty" yaml:"methods,omitempty"`
1112
Host string `json:"host,omitempty" yaml:"host,omitempty"`
1213
Hosts []string `json:"hosts,omitempty" yaml:"hosts,omitempty"`

pkg/cmd/config/configutil/configutil.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,15 @@ func fetchPluginMetadata(client *api.Client, query map[string]string) ([]api.Plu
462462
if cmdutil.IsNotFound(err) {
463463
continue
464464
}
465+
// Skip plugins that don't support metadata (e.g., "plugin doesn't
466+
// have metadata_schema"). This is common in API7 EE where some
467+
// plugins expose the metadata endpoint but return an error.
468+
errMsg := err.Error()
469+
if strings.Contains(errMsg, "metadata_schema") ||
470+
strings.Contains(errMsg, "doesn't have") ||
471+
cmdutil.IsOptionalResourceError(err) {
472+
continue
473+
}
465474
return nil, err
466475
}
467476

pkg/cmd/credential/create/create.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type Options struct {
2323
GatewayGroup string
2424
Consumer string
2525
File string
26+
ID string
2627

2728
Desc string
2829
PluginsJSON string
@@ -32,13 +33,16 @@ type Options struct {
3233
func NewCmd(f *cmd.Factory) *cobra.Command {
3334
opts := &Options{IO: f.IOStreams, Client: f.HttpClient, Config: f.Config}
3435
c := &cobra.Command{
35-
Use: "create",
36+
Use: "create [id]",
3637
Short: "Create a credential",
37-
Args: cobra.NoArgs,
38+
Args: cobra.MaximumNArgs(1),
3839
RunE: func(c *cobra.Command, args []string) error {
3940
opts.Output, _ = c.Flags().GetString("output")
4041
opts.GatewayGroup, _ = c.Flags().GetString("gateway-group")
4142
opts.Consumer, _ = c.Flags().GetString("consumer")
43+
if len(args) > 0 {
44+
opts.ID = args[0]
45+
}
4246
return actionRun(opts)
4347
},
4448
}
@@ -75,6 +79,10 @@ func actionRun(opts *Options) error {
7579
return err
7680
}
7781

82+
if opts.ID != "" {
83+
payload["id"] = opts.ID
84+
}
85+
7886
httpClient, err := opts.Client()
7987
if err != nil {
8088
return err

pkg/cmd/route/create/create.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type Options struct {
2525

2626
Name string
2727
URI string
28+
Paths []string
2829
Methods []string
2930
Host string
3031
ServiceID string
@@ -48,7 +49,8 @@ func NewCmd(f *cmd.Factory) *cobra.Command {
4849

4950
c.Flags().StringVar(&opts.Name, "name", "", "Route name")
5051
c.Flags().StringVarP(&opts.File, "file", "f", "", "Path to JSON/YAML file with resource definition")
51-
c.Flags().StringVar(&opts.URI, "uri", "", "Route URI")
52+
c.Flags().StringVar(&opts.URI, "uri", "", "Route URI (single path, APISIX compat)")
53+
c.Flags().StringSliceVar(&opts.Paths, "path", nil, "Route path (repeatable, API7 EE format)")
5254
c.Flags().StringSliceVar(&opts.Methods, "methods", nil, "Allowed HTTP methods")
5355
c.Flags().StringVar(&opts.Host, "host", "", "Route host")
5456
c.Flags().StringVar(&opts.ServiceID, "service-id", "", "Bound service ID")
@@ -100,8 +102,18 @@ func actionRun(opts *Options) error {
100102
}
101103
return cmdutil.NewExporter(format, opts.IO.Out).Write(json.RawMessage(body))
102104
}
103-
if opts.URI == "" {
104-
return fmt.Errorf("--uri is required")
105+
if opts.URI == "" && len(opts.Paths) == 0 {
106+
return fmt.Errorf("--path or --uri is required")
107+
}
108+
109+
if opts.Name == "" {
110+
return fmt.Errorf("--name is required for flag-based route creation")
111+
}
112+
113+
// Convert --uri to Paths for API7 EE compatibility.
114+
paths := opts.Paths
115+
if len(paths) == 0 && opts.URI != "" {
116+
paths = []string{opts.URI}
105117
}
106118

107119
httpClient, err := opts.Client()
@@ -120,7 +132,7 @@ func actionRun(opts *Options) error {
120132

121133
bodyReq := api.Route{
122134
Name: opts.Name,
123-
URI: opts.URI,
135+
Paths: paths,
124136
Methods: opts.Methods,
125137
Host: opts.Host,
126138
ServiceID: opts.ServiceID,

pkg/cmd/route/get/get.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/http"
7+
"strings"
78

89
"github.com/spf13/cobra"
910

@@ -81,6 +82,7 @@ func actionRun(opts *Options) error {
8182
tp.AddRow("id", item.ID)
8283
tp.AddRow("name", item.Name)
8384
tp.AddRow("uri", item.URI)
85+
tp.AddRow("paths", strings.Join(item.Paths, ", "))
8486
tp.AddRow("host", item.Host)
8587
tp.AddRow("service_id", item.ServiceID)
8688
tp.AddRow("upstream_id", item.UpstreamID)

pkg/cmd/route/list/list.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type Options struct {
2323
Output string
2424
GatewayGroup string
2525
Label string
26+
ServiceID string
2627
}
2728

2829
func NewCmd(f *cmd.Factory) *cobra.Command {
@@ -36,10 +37,12 @@ func NewCmd(f *cmd.Factory) *cobra.Command {
3637
opts.Output, _ = c.Flags().GetString("output")
3738
opts.GatewayGroup, _ = c.Flags().GetString("gateway-group")
3839
opts.Label, _ = c.Flags().GetString("label")
40+
opts.ServiceID, _ = c.Flags().GetString("service-id")
3941
return actionRun(opts)
4042
},
4143
}
4244
c.Flags().StringVar(&opts.Label, "label", "", "Filter by label (key=value)")
45+
c.Flags().StringVar(&opts.ServiceID, "service-id", "", "Filter by service ID (required by API7 EE)")
4346
return c
4447
}
4548

@@ -64,6 +67,9 @@ func actionRun(opts *Options) error {
6467

6568
client := api.NewClient(httpClient, cfg.BaseURL())
6669
query := map[string]string{"gateway_group_id": ggID}
70+
if opts.ServiceID != "" {
71+
query["service_id"] = opts.ServiceID
72+
}
6773
labelKey, labelValue := cmdutil.ParseLabel(opts.Label)
6874
if labelKey != "" {
6975
query["label"] = labelKey
@@ -94,13 +100,16 @@ func actionRun(opts *Options) error {
94100
}
95101

96102
tp := tableprinter.New(opts.IO.Out)
97-
tp.SetHeaders("ID", "NAME", "URI", "METHODS", "STATUS")
103+
tp.SetHeaders("ID", "NAME", "PATHS", "METHODS", "STATUS")
98104
for _, item := range resp.List {
99-
uri := item.URI
100-
if uri == "" && len(item.URIs) > 0 {
101-
uri = strings.Join(item.URIs, ",")
105+
paths := strings.Join(item.Paths, ",")
106+
if paths == "" {
107+
paths = item.URI
108+
if paths == "" && len(item.URIs) > 0 {
109+
paths = strings.Join(item.URIs, ",")
110+
}
102111
}
103-
tp.AddRow(item.ID, item.Name, uri, strings.Join(item.Methods, ","), fmt.Sprintf("%d", item.Status))
112+
tp.AddRow(item.ID, item.Name, paths, strings.Join(item.Methods, ","), fmt.Sprintf("%d", item.Status))
104113
}
105114

106115
return tp.Render()

pkg/cmd/route/list/list_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ func TestListRoutes_Table(t *testing.T) {
8181
if !strings.Contains(output, "NAME") {
8282
t.Error("table should contain NAME header")
8383
}
84-
if !strings.Contains(output, "URI") {
85-
t.Error("table should contain URI header")
84+
if !strings.Contains(output, "PATHS") {
85+
t.Error("table should contain PATHS header")
8686
}
8787
if !strings.Contains(output, "METHODS") {
8888
t.Error("table should contain METHODS header")

0 commit comments

Comments
 (0)