diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9da44db..df01f70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,12 +49,14 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: 'stable' + # Lint at the module's own Go. golangci-lint is a prebuilt binary + # that can only load stdlib export data from the Go it was built + # with; following 'stable' turns every Go release into a red lint + # job on every PR, triggered by nothing in the repo. + go-version-file: go.mod # golangci-lint v2 subsumes gofmt, gci, staticcheck, govet and the rest; # config in .golangci.yml. Generated files are auto-excluded by header. - # The official action ships a golangci-lint binary whose type-checker - # matches the Go toolchain, avoiding stdlib version skew. - name: golangci-lint uses: golangci/golangci-lint-action@v9 with: diff --git a/flashduty_test.go b/flashduty_test.go index 6a01336..fa1cda9 100644 --- a/flashduty_test.go +++ b/flashduty_test.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "net/http/httptest" + "reflect" "strings" "testing" "time" @@ -274,6 +275,42 @@ func TestIncidentNotificationOverrideOmitsUnsetPreference(t *testing.T) { } } +func TestCustomFieldValuesReachTheWire(t *testing.T) { + c, _ := NewClient("KEY", WithBaseURL("https://api.flashcat.cloud"), WithLogger(noopLogger{})) + values := CustomFieldValues{"region": "cn-beijing", "impact_users": float64(42), "tags": []any{"db"}} + + tests := []struct { + name string + path string + body any + field string + }{ + {"create incident", "/incident/create", &CreateIncidentRequest{IncidentSeverity: "Critical", Fields: values}, "fields"}, + {"ack incident", "/incident/ack", &AckIncidentRequest{CustomFields: values}, "custom_fields"}, + {"resolve incident", "/incident/resolve", &ResolveIncidentRequest{CustomFields: values}, "custom_fields"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req, err := c.newRequest(context.Background(), http.MethodPost, tt.path, tt.body) + if err != nil { + t.Fatal(err) + } + body, err := io.ReadAll(req.Body) + if err != nil { + t.Fatal(err) + } + var payload map[string]any + if err := json.Unmarshal(body, &payload); err != nil { + t.Fatal(err) + } + got, _ := payload[tt.field].(map[string]any) + if !reflect.DeepEqual(got, map[string]any(values)) { + t.Fatalf("%s = %#v, want %#v (body = %s)", tt.field, payload[tt.field], values, body) + } + }) + } +} + func TestNewRequestAppliesHookAndHeaders(t *testing.T) { c, _ := NewClient("KEY", WithRequestHeaders(map[string][]string{"X-Static": {"s"}}), diff --git a/internal/cmd/gen/main.go b/internal/cmd/gen/main.go index 60f97f1..13feae9 100644 --- a/internal/cmd/gen/main.go +++ b/internal/cmd/gen/main.go @@ -646,6 +646,10 @@ func (g *Gen) emitModels() string { // a map payload (e.g. name -> count), not a struct. v := g.goTypeOf(asMap(s["additionalProperties"]), goName(name)+"Value") fmt.Fprintf(&enumsAndAliases, "// %s is a map response payload.\ntype %s map[string]%s\n\n", goName(name), goName(name), v) + case typeStr(s) == "object" && len(asMap(s["properties"])) == 0 && s["additionalProperties"] == true: + // A bare object with additionalProperties: true carries arbitrary + // keys (e.g. custom field values); a struct would drop them all. + fmt.Fprintf(&enumsAndAliases, "// %s is a free-form JSON object.\ntype %s map[string]any\n\n", goName(name), goName(name)) case typeStr(s) == "string", typeStr(s) == "integer", typeStr(s) == "number", typeStr(s) == "boolean": gt := g.goTypeOf(s, goName(name)) fmt.Fprintf(&enumsAndAliases, "type %s %s\n\n", goName(name), gt) diff --git a/models_gen.go b/models_gen.go index 3a672e7..1d36a16 100644 --- a/models_gen.go +++ b/models_gen.go @@ -26,6 +26,9 @@ func (e AlertFeedType) String() string { return string(e) } // AlertRuleExportListResponse is a list response payload. type AlertRuleExportListResponse []AlertRuleExport +// CustomFieldValues is a free-form JSON object. +type CustomFieldValues map[string]any + // DataSourceListResponse is a list response payload. type DataSourceListResponse []DataSourceItem @@ -2420,9 +2423,6 @@ type CreateWorkItemRequest struct { Title string `json:"title" toon:"title"` } -// CustomFieldValues is generated from the Flashduty OpenAPI schema. -type CustomFieldValues struct{} - // DsClickHouseConfig is generated from the Flashduty OpenAPI schema. type DsClickHouseConfig struct { // Default database for authentication.